Not
Bu sayfaya erişim yetkilendirme gerektiriyor. Oturum açmayı veya dizinleri değiştirmeyi deneyebilirsiniz.
Bu sayfaya erişim yetkilendirme gerektiriyor. Dizinleri değiştirmeyi deneyebilirsiniz.
Bu konu C++'ta bulunan çeşitli değer kategorilerini (ve değerlere başvurularını) tanıtır ve açıklar:
- glvalue
- lvalue
- xlvalue
- prvalue
- rvalue (değer)
lvalues ve rvaluesşüphesiz duymuşsunuzdur. Ancak bunları bu konunun sunduğu terimlerle düşünmeyebilirsiniz.
C++ içindeki her ifade, yukarıda listelenen beş kategoriden birine ait bir değer verir. C++ dilinin özellikleri ve kuralları, bu değer kategorilerinin ve bunlara yönelik başvuruların düzgün bir şekilde anlaşılmasını talep eder. Bu özellikler arasında bir değerin adresini almak, bir değeri kopyalamak, bir değeri taşımak ve başka bir işleve iletmek yer alır. Bu konu, bu yönlerin tümüne ayrıntılı olarak girmez, ancak bunların sağlam bir şekilde anlaşılması için temel bilgiler sağlar.
Bu konudaki bilgiler, Stroustrup'un değer kategorilerini kimlik ve taşınabilirliğin iki bağımsız özelliğiyle çözümlemesi açısından çerçevelendirilir [Stroustrup, 2013].
Bir lvalue'nin kimliği vardır
kimliğinesahip bir değerin ne anlama geldiği nedir? Bir değerin bellek adresine sahipseniz (veya alabilirseniz) ve bunu güvenli bir şekilde kullanıyorsanız, değerin kimliği vardır. Bu şekilde, değerlerin içeriğini karşılaştırmaktan daha fazlasını yapabilirsiniz; bunları kimliğe göre karşılaştırabilir veya ayırt edebilirsiniz.
lvalue kimliğe sahiptir. Artık "lvalue" içindeki "l"nin "sol" kısaltması olması (ödevin sol tarafında olduğu gibi) yalnızca geçmişe dönük bir konudur. C++ dilinde, bir lvalue atamanın solunda 'dan önce veya sağında'den sonra yer alabilir. "lvalue" içindeki "l", aslında ne olduklarını anlamanıza veya tanımlamanıza yardımcı olmaz. Yalnızca lvalue olarak adlandırdığımız değerin kimliğe sahip bir değer olduğunu anlamanız gerekir.
lvalues ifadelerine örnek olarak şunlar verilebilir: adlandırılmış değişken veya sabit; veya başvuru döndüren bir işlev.
int& get_by_ref() { ... }
int get_by_val() { ... }
int main()
{
std::vector<byte> vec{ 99, 98, 97 };
std::vector<byte>* addr1{ &vec }; // ok: vec is an lvalue.
int* addr2{ &get_by_ref() }; // ok: get_by_ref() is an lvalue.
int* addr3{ &(get_by_ref() + 1) }; // Error: get_by_ref() + 1 is not an lvalue.
int* addr4{ &get_by_val() }; // Error: get_by_val() is not an lvalue.
}
Şimdi, lvalue'ların bir kimliğe sahip olduğu doğru bir ifade olsa da, bu xvalue'lar için de geçerlidir. Bu konunun ilerleyen bölümlerinde bir xvalue tam olarak ne olduğunu göreceğiz. Şimdilik, glvalue ("genelleştirilmiş lvalue" için) adlı bir değer kategorisi olduğunu unutmayın. glvalues kümesi, hem lvalues (klasik lvaluesolarak da bilinir) hem de xvalues'un üst kümesidir. Bu nedenle, "bir lvalue'nun kimliği vardır" doğru olsa da, bu çizimde gösterildiği gibi, kimliği olan öğelerin tam kümesi glvalues kümesidir.
Rvalue taşınabilirdir; lvalue taşınamaz
Ancak glvalue olmayan değerler vardır. Başka bir deyişle, için bir bellek adresi (veya geçerli olması için buna güvenemezsiniz) değerleri vardır. Yukarıdaki kod örneğinde bu tür değerler gördük.
Güvenilir bir bellek adresi olmaması bir dezavantaj gibi görünüyor. Ancak bunun gibi bir değerin avantajı, kopyalamak yerine taşıyabilmenizdir (genellikle ucuzdur). Bir değerin taşınması, değerin artık eskiden olduğu yerde olmadığı anlamına gelir. Bu nedenle, eskiden olduğu yerde erişmeye çalışmak kaçınılması gereken bir şeydir. Bir değeri ne zaman ve nasıl taşıyacağınızın
"rvalue" içindeki "r", "sağ" anlamının kısaltmasıdır (yani, bir atama işleminin sağ tarafı gibi). Ancak, atamaların dışında rvalue'ları ve rvalue referanslarını kullanabilirsiniz. "rvalue" içindeki "r", odaklanılması gereken şey değildir. Yalnızca rvalue olarak adlandırdığımız değerin taşınabilir bir değer olduğunu anlamanız gerekir.
Buna karşılık, bu çizimde gösterildiği gibi bir lvalue taşınamaz. Bir lvalue taşınacaksa, bu lvaluetanımıyla çelişir. Ayrıca, kodun lvalue'ya erişmeye devam etmesinin makul bir şekilde beklendiği durumlarda ortaya çıkan beklenmedik bir sorun olacaktır.
Bu nedenle bir lvalue'yi taşıyamazsınız. Ancak, ne yaptığınızı biliyorsanız (taşımadan sonra erişmemeye dikkat etmek de dahil) taşıyabileceğiniz bir tür glvalue (kimlikli öğe kümesi)
Rvalue referansları ve referans bağlama kuralları
Bu bölümde bir rvalue'e başvuru için söz dizimi tanıtılmıştır. Taşıma ve iletme konusunu detaylı bir şekilde ele almak için başka bir konunun gündeme gelmesini beklememiz gerekecek, ancak rvalue başvurularının bu sorunların çözümünde gerekli bir parça olduğunu söylemek yeterlidir. Ancak rvalue referanslarına bakmadan önce, daha önce sadece "referans" olarak adlandırdığımız T&hakkında daha net olmamız gerekiyor. Bu gerçekten "bir lvalue (const olmayan) başvurusu", başvuru kullanıcısının yazabileceği bir değere başvuruyor.
template<typename T> T& get_by_lvalue_ref() { ... } // Get by lvalue (non-const) reference.
template<typename T> void set_by_lvalue_ref(T&) { ... } // Set by lvalue (non-const) reference.
Lvalue referansı bir lvalue'ya bağlanabilir, ancak bir rvalue'ya bağlanamaz.
Ardından, başvuru kullanıcısının yazma (örneğin, sabit)
template<typename T> T const& get_by_lvalue_cref() { ... } // Get by lvalue const reference.
template<typename T> void set_by_lvalue_cref(T const&) { ... } // Set by lvalue const reference.
Bir lvalue const başvurusu bir lvalue'ya veya rvalue'ya bağlanabilir.
T türünde bir rvalue başvurusunun söz dizimi T&&olarak yazılır. Rvalue başvurusu, taşınabilen bir değeri ifade eder; bu değeri kullandıktan sonra içeriğini korumamız gerekmeyen bir değerdir (örneğin, geçici bir değer). "Asıl amaç, bir rvalue referansına bağlı değerin hareket ettirilerek (ve böylece değiştirilerek) elde edilmesi olduğundan, const ve volatile niteleyicileri (cv-niteleyicileri olarak da bilinir) rvalue referanslarına uygulanmaz."
template<typename T> T&& get_by_rvalue_ref() { ... } // Get by rvalue reference.
struct A { A(A&& other) { ... } }; // A move constructor takes an rvalue reference.
Rvalue başvurusu bir rvalue'ya bağlanır. Aslında, aşırı yükleme çözümlemesi açısından bir rvalue lvalue const başvurusu yerine rvalue başvurusuna bağlanmayı tercih eder. Ancak rvalue başvurusu bir lvalue'ya bağlanamaz çünkü dediğimiz gibi rvalue başvurusu, içeriğini korumamız gerekmediği varsayılan bir değere (taşıma oluşturucusunun parametresi gibi) başvuruyor.
Ayrıca, bir değere göre bağımsız değişken beklendiğinde kopyalama yoluyla bir rvalue da geçirebilirsiniz (veya rvalue bir xvalue ise taşıma yapısı yoluyla).
Bir glvalue'un kimliği vardır; prvalue'un yoktur.
Bu aşamada neyin kimliğe sahip olduğunu biliyoruz. Taşınabilen ve olmayan şeyleri de biliyoruz. Ancak henüz kimliği
int& get_by_ref() { ... }
int get_by_val() { ... }
int main()
{
int* addr3{ &(get_by_ref() + 1) }; // Error: get_by_ref() + 1 is a prvalue.
int* addr4{ &get_by_val() }; // Error: get_by_val() is a prvalue.
}
Değer kategorilerinin tam resmi
Tek yapmamız gereken, yukarıdaki bilgi ve çizimleri tek ve büyük bir resimde birleştirmektir.
glvalue (i)
Bir glvalue (genelleştirilmiş lvalue) kimliğine sahiptir. "Kimliği olan" kısaltması olarak "i" kullanacağız.
lvalue (i!m)
Bir lvalue (bir tür glvalue) kimliğine sahiptir, ancak taşınamaz. Bunlar genellikle referansla, sabit referansla veya kopyalamanın ucuz olduğu durumlarda doğrudan değerle ilettiğiniz okuma-yazma değerleridir. Bir lvalue, bir rvalue başvurusuna bağlanamaz.
xvalue (i&m)
Bir xvalue (bir tür glvalue, ancak aynı zamanda bir tür rvalue) kimliğine sahiptir ve aynı zamanda taşınabilir. Bu, kopyalamanın maliyetli olması nedeniyle taşımaya karar verdiğiniz eski bir lvalue olabilir ve ardından ona erişmemeye dikkat edersiniz. Bir lvalue değerini xvalue'ya dönüştürme burada açıklanabilir.
struct A { ... };
A a; // a is an lvalue...
static_cast<A&&>(a); // ...but this expression is an xvalue.
Yukarıdaki kod örneğinde henüz hiçbir şey taşımadık. Bir lvalue değerini adsız bir rvalue referansına dökümleyerek sadece bir xvalue oluşturduk. Yine de lvalue adıyla tanımlanabilir; ancak xvalue olarak artık taşınabilir hale gelmiştir. Taşınma nedenleri ve taşınma işleminin gerçekte nasıl göründüğü, başka bir konuyu beklemek durumunda kalacaktır. Eğer bu şekilde yardımcı olursa, "xvalue" içindeki "x" harfini "sadece uzmanlara yönelik" olarak düşünebilirsiniz. Bir lvalue değerini bir xvalue'a (bir tür rvalue, unutmayın) dönüştürerek, değer daha sonra bir rvalue referansına bağlanabilecek hale gelir.
Burada iki xvalue örneği daha verilmiştir: adlandırılmamış rvalue başvurusu döndüren bir işlevi çağırma ve bir xvalue üyesine erişme.
struct A { int m; };
A&& f();
f(); // This expression is an xvalue...
f().m; // ...and so is this.
prvalue (!i&m)
Prvalue (temiz rvalue; bir tür rvalue) kendine ait bir kimliği yoktur, ancak taşınabilir. Bunlar genellikle geçicidir veya değere göre döndüren bir işlevi çağırmanın sonucu ya da glvalue olmayan diğer ifadeleri değerlendirmenin sonucu olur.
rvalue (m)
Rvalue taşınabilir. "m" değerini "taşınabilir" kısaltması olarak kullanacağız.
Rvalue referansı her zaman bir rvalue'ya (içeriğini korumamız gerekmediği varsayılan bir değer) işaret eder.
Ancak bir rvalue referansı bir rvalue mu? adlandırılmamış rvalue başvurusu (yukarıdaki xvalue kod örneklerinde gösterilenler gibi) bir xvalue olduğundan, evet, bir rvalue'dur. Taşıma oluşturucusununki gibi bir rvalue başvuru işlevi parametresine bağlanmayı tercih eder. Buna karşılık (ve belki de sezgisel olmayan) bir rvalue başvurusunun adı varsa, bu addan oluşan ifade bir lvalue olur. Bu nedenle bir rvalue başvuru parametresine bağlı. Ancak bunu yapmak kolaydır; bunu yeniden adlandırılmamış bir rvalue başvurusuna (xvalue) dönüştürmeniz gerekir.
void foo(A&) { ... }
void foo(A&&) { ... }
void bar(A&& a) // a is a named rvalue reference; so it's an lvalue.
{
foo(a); // Calls foo(A&).
foo(static_cast<A&&>(a)); // Calls foo(A&&).
}
A&& get_by_rvalue_ref() { ... } // This unnamed rvalue reference is an xvalue.
!i&!m
Kimliği olmayan ve taşınamayan değer türü, henüz ele almadığımız tek birleşimdir. Ancak bu kategori C++ dilinde yararlı bir fikir olmadığından bunu göz ardı edebiliriz.
Referans çökertme kuralları
Bir ifadedeki birden çok benzer başvuru (lvalue başvurusuna yapılan lvalue başvurusu veya rvalue başvurusuna yapılan rvalue başvurusu) birbirini karşılıklı olarak iptal eder.
-
A& &A&'e çöker. -
A&& &&A&&'e çöker.
bir ifadedeki birden çok farklı türdeki referans, lvalue referansına dönüştürülür.
-
A& &&A&'e çöker. -
A&& &A&'e çöker.
İleriye Yönlendirme Referansları
Bu son bölüm, üzerinde daha önce ele aldığımız rvalue başvurularınıfarklı bir
void foo(A&& a) { ... }
-
A&&, gördüğümüz gibi bir rvalue referansıdır. Const ve volatile, rvalue referanslarına uygulanmaz. yalnızca A türünde rvalue'ları kabul eder. - Rvalue başvurularının (
A&&gibi) mevcut olmasının nedeni, geçirilmekte olan geçici (veya başka bir rvalue) durumu için iyileştirilmiş bir aşırı yükleme yazabilmenizdir.
template <typename _Ty> void bar(_Ty&& ty) { ... }
,bir iletme başvurusudur. bar'a ne geçirdiğinize bağlı olarak, _Ty türü volatile/non-volatile'den bağımsız olarak sabit/sabit olmayan olabilir.-
bar_Tytüründe herhangi bir lvalue veya rvalue kabul eder. - Bir lvalue geçirmek, iletme referansının
_Ty& &&olmasına neden olur ve bu da_Ty&lvalue referansına daraltılır. - Rvalue geçirmek, iletme başvurusunun
_Ty&&rvalue başvurusu olmasına neden olur. - yönlendirme referanslarının (
gibi) var olma sebebi, iyileştirme için değil, onlara ilettiğiniz bilgileri alıp şeffaf ve verimli bir şekilde iletmektir. Bir yönlendirme referansıyla karşılaşma olasılığınız yalnızca kitaplık kodu yazdığınızda (veya yakından incelediğinizde) (örneğin, oluşturucu bağımsız değişkenlerini yönlendiren bir fabrika fonksiyonu).
Kaynaklar
- [Stroustrup, 2013] B. Stroustrup: The C++ Programming Language, Fourth Edition. Addison-Wesley. 2013.