Aracılığıyla paylaş


Rvalue başvuru bildirimcisi: &&

Bir rvalue ifadesine başvuru tutar.

Sözdizimi

rvalue-reference-type-id:
type-specifier-seq&& attribute-specifier-seqtercih et ptr-abstract-declarator

Açıklamalar

Rvalue başvuruları, bir lvalue değerini rvalue'dan ayırt etmenizi sağlar. Lvalue başvuruları ve rvalue başvuruları, benzer ve benzerdir, ancak biraz farklı kurallara uyarlar. lvalues ve rvalues hakkında daha fazla bilgi için bkz . Lvalues ve Rvalues. lvalue başvuruları hakkında daha fazla bilgi için bkz . Lvalue Başvuru Bildirimcisi: &.

Aşağıdaki bölümlerde rvalue başvurularının taşıma semantiğinin ve mükemmel iletmenin uygulanmasını nasıl desteklediği açıklanmaktadır.

Semantiği taşıma

Rvalue başvuruları, taşıma semantiğinin uygulanmasını destekler ve bu da uygulamalarınızın performansını önemli ölçüde artırabilir. Taşıma semantiği, kaynakları (dinamik olarak ayrılan bellek gibi) bir nesneden diğerine aktaran kod yazmanızı sağlar. Taşıma semantiği, geçici nesnelerden kaynak aktarımına olanak sağladığından çalışır: programın başka bir yerinde başvurulamaz.

Taşıma semantiğini uygulamak için genellikle sınıfınıza bir taşıma oluşturucu ve isteğe bağlı olarak bir taşıma atama işleci ()operator= sağlarsınız. Kaynakları rvalue olan kopyalama ve atama işlemleri, taşıma semantiğinden otomatik olarak yararlanır. Varsayılan kopya oluşturucusunun aksine, derleyici varsayılan bir taşıma oluşturucu sağlamaz. Taşıma oluşturucu yazma ve kullanma hakkında daha fazla bilgi için bkz . Oluşturucuları taşıma ve atama işleçlerini taşıma.

Ayrıca taşıma semantiğinden yararlanmak için normal işlevleri ve işleçleri aşırı yükleyebilirsiniz. Visual Studio 2010, taşıma semantiğini C++ Standart Kitaplığı'na tanıtır. Örneğin, string sınıfı taşıma semantiği kullanan işlemleri uygular. Birkaç dizeyi birleştirir ve sonucu yazdıran aşağıdaki örneği göz önünde bulundurun:

// string_concatenation.cpp
// compile with: /EHsc
#include <iostream>
#include <string>
using namespace std;

int main()
{
   string s = string("h") + "e" + "ll" + "o";
   cout << s << endl;
}

Visual Studio 2010'den önce, her çağrısı operator+ yeni bir geçici string nesne (rvalue) ayırır ve döndürür. operator+ kaynak dizelerin lvalues veya rvalues olup olmadığını bilmediğinden bir dizeyi diğerine ekleyemez. Kaynak dizelerin her ikisi de lvalue ise, programın başka bir yerinde bunlara başvurulabilir ve bu nedenle değiştirilmemelidir. rvalue başvurularını kullanarak rvalue değerlerini alacak şekilde değiştirebilirsiniz operator+ ; bu, programın başka bir yerinde başvurulamaz. Bu değişiklikle artık operator+ bir dizeyi başka bir dizeye ekleyebilir. Değişiklik, sınıfın yapması gereken dinamik bellek ayırmalarının string sayısını önemli ölçüde azaltır. Sınıf hakkında string daha fazla bilgi için bkz basic_string . Sınıf.

Taşıma semantiği, derleyici Dönüş Değeri İyileştirme (RVO) veya Adlandırılmış Dönüş Değeri İyileştirme (NRVO) kullanamıyorsa da yardımcı olur. Bu durumlarda, tür tanımlıyorsa derleyici taşıma oluşturucuyu çağırır.

Taşıma semantiğini daha iyi anlamak için bir nesneye vector öğe ekleme örneğini düşünün. Nesnenin vector kapasitesi aşılırsa, vector nesnenin öğeleri için yeterli belleği yeniden ayırması ve sonra eklenen öğeye yer açmak için her öğeyi başka bir bellek konumuna kopyalaması gerekir. Ekleme işlemi bir öğeyi kopyaladığında ilk olarak yeni bir öğe oluşturur. Ardından önceki öğedeki verileri yeni öğeye kopyalamak için kopya oluşturucuyu çağırır. Son olarak, önceki öğeyi yok eder. Taşıma semantiği, pahalı bellek ayırma ve kopyalama işlemleri yapmak zorunda kalmadan nesneleri doğrudan taşımanızı sağlar.

Örnekteki taşıma semantiğinden vector yararlanmak için, verileri bir nesneden diğerine taşımak için bir taşıma oluşturucu yazabilirsiniz.

Visual Studio 2010'da taşıma semantiğiniN C++ Standart Kitaplığına girişi hakkında daha fazla bilgi için bkz . C++ Standart Kitaplığı.

Mükemmel iletme

Mükemmel iletme, aşırı yüklenmiş işlevlere olan ihtiyacı azaltır ve iletme sorununu önlemeye yardımcı olur. İletme sorunu , başvuruları parametresi olarak alan genel bir işlev yazdığınızda oluşabilir. Bu parametreleri başka bir işleve geçirirse (veya iletirse), örneğin türünde const T&bir parametre alırsa, çağrılan işlev bu parametrenin değerini değiştiremez. Genel işlev türünde T&bir parametre alırsa, işlev rvalue (geçici bir nesne veya tamsayı değişmez değeri gibi) kullanılarak çağrılamaz.

Normalde, bu sorunu çözmek için, genel işlevin hem hem de T& const T& parametrelerinin her biri için aşırı yüklenmiş sürümleri sağlamanız gerekir. Sonuç olarak, aşırı yüklenmiş işlevlerin sayısı parametre sayısıyla üstel olarak artar. Rvalue başvuruları, bir işlevin rastgele bağımsız değişkenleri kabul eden bir sürümünü yazmanızı sağlar. Ardından bu işlev, diğer işlev doğrudan çağrılmış gibi bunları başka bir işleve iletebilir.

, , XYve Zolmak üzere dört tür Wbildiren aşağıdaki örneği göz önünde bulundurun. Her türün oluşturucu, parametreleri olarak farklı ve lvalue olmayanconst başvuruların bir bileşimini const alır.

struct W
{
   W(int&, int&) {}
};

struct X
{
   X(const int&, int&) {}
};

struct Y
{
   Y(int&, const int&) {}
};

struct Z
{
   Z(const int&, const int&) {}
};

Nesneleri oluşturan genel bir işlev yazmak istediğinizi varsayalım. Aşağıdaki örnekte bu işlevi yazmanın bir yolu gösterilmektedir:

template <typename T, typename A1, typename A2>
T* factory(A1& a1, A2& a2)
{
   return new T(a1, a2);
}

Aşağıdaki örnekte işleve yapılan geçerli bir çağrı gösterilmektedir factory :

int a = 4, b = 5;
W* pw = factory<W>(a, b);

Ancak, aşağıdaki örnek işleve factory geçerli bir çağrı içermiyor. Bunun nedeni factory , parametreleri olarak değiştirilebilen lvalue başvurularını almasıdır, ancak rvalues kullanılarak çağrılır:

Z* pz = factory<Z>(2, 2);

Normalde, bu sorunu çözmek için ve const A& parametrelerinin her birleşimi A& için işlevin factory aşırı yüklenmiş bir sürümünü oluşturmanız gerekir. Rvalue başvuruları, aşağıdaki örnekte gösterildiği gibi işlevin factory bir sürümünü yazmanızı sağlar:

template <typename T, typename A1, typename A2>
T* factory(A1&& a1, A2&& a2)
{
   return new T(std::forward<A1>(a1), std::forward<A2>(a2));
}

Bu örnek, işlevin parametreleri olarak rvalue başvurularını factory kullanır. işlevinin std::forward amacı, fabrika işlevinin parametrelerini şablon sınıfının oluşturucusunun ilettiği yerdir.

Aşağıdaki örnekte , , Xve sınıflarının Wörneklerini oluşturmak için düzeltilmiş factory işlevi kullanan işlev gösterilmektedirmain.Z Y Düzeltilen factory işlev, parametrelerini (lvalues veya rvalues) uygun sınıf oluşturucusna iletir.

int main()
{
   int a = 4, b = 5;
   W* pw = factory<W>(a, b);
   X* px = factory<X>(2, b);
   Y* py = factory<Y>(a, 2);
   Z* pz = factory<Z>(2, 2);

   delete pw;
   delete px;
   delete py;
   delete pz;
}

Rvalue başvurularının özellikleri

Lvalue başvurusu ve rvalue başvurusu almak için bir işlevi aşırı yükleyebilirsiniz.

Lvalue başvurusu veya rvalue başvurusu almak const için bir işlevi aşırı yükleyerek, değiştirilemeyen nesneler (lvalues) ile değiştirilebilir geçici değerler (rvalue) arasında ayrım yapmak için kod yazabilirsiniz. Nesne olarak constişaretlenmediği sürece bir nesneyi rvalue başvurusu alan bir işleve geçirebilirsiniz. Aşağıdaki örnekte, lvalue başvurusu ve rvalue başvurusu almak için aşırı yüklenmiş işlevi fgösterilmektedir. main İşlev hem lvalues hem de rvalue ile çağırırf.

// reference-overload.cpp
// Compile with: /EHsc
#include <iostream>
using namespace std;

// A class that contains a memory resource.
class MemoryBlock
{
   // TODO: Add resources for the class here.
};

void f(const MemoryBlock&)
{
   cout << "In f(const MemoryBlock&). This version can't modify the parameter." << endl;
}

void f(MemoryBlock&&)
{
   cout << "In f(MemoryBlock&&). This version can modify the parameter." << endl;
}

int main()
{
   MemoryBlock block;
   f(block);
   f(MemoryBlock());
}

Bu örnek aşağıdaki çıkışı oluşturur:

In f(const MemoryBlock&). This version can't modify the parameter.
In f(MemoryBlock&&). This version can modify the parameter.

Bu örnekte, ilk çağrısı f bağımsız değişkeni olarak bir yerel değişkeni (lvalue) geçirir. İkinci çağrısı f , bağımsız değişkeni olarak geçici bir nesne geçirir. Geçici nesneye programın başka bir yerinde başvurulamadığından, çağrı aşırı yüklenmiş sürümüne f bağlanır ve bu, nesneyi değiştirmekte serbest olan bir rvalue başvurusu alır.

Derleyici, adlandırılmış rvalue başvurularını lvalue, adsız rvalue başvurularını ise rvalue olarak ele alır.

Parametre olarak rvalue başvurusu alan işlevler, parametreyi işlevin gövdesinde lvalue olarak kabul eder. Derleyici, adlandırılmış rvalue başvurularını lvalue olarak ele alır. Bunun nedeni, adlandırılmış bir nesneye programın çeşitli bölümleri tarafından başvurulabilmesidir. Bir programın birden çok bölümünün bu nesnedeki kaynakları değiştirmesine veya kaldırmasına izin vermek tehlikelidir. Örneğin, bir programın birden çok bölümü aynı nesneden kaynak aktarmayı denerse, yalnızca ilk aktarım başarılı olur.

Aşağıdaki örnekte, lvalue başvurusu ve rvalue başvurusu almak için aşırı yüklenmiş işlevi ggösterilmektedir. İşlev f , parametresi olarak bir rvalue başvurusu (adlandırılmış rvalue başvurusu) alır ve bir rvalue başvurusu (adlandırılmamış rvalue başvurusu) döndürür. öğesinden fçağrısında g aşırı yükleme çözümlemesi, gövdesi f parametresini g bir lvalue olarak değerlendirdiğinden lvalue başvurusu alan sürümünü seçer. öğesinden mainçağrısında g aşırı yükleme çözümlemesi, rvalue başvurusu döndürdüğünden g f rvalue başvurusu alan sürümünü seçer.

// named-reference.cpp
// Compile with: /EHsc
#include <iostream>
using namespace std;

// A class that contains a memory resource.
class MemoryBlock
{
   // TODO: Add resources for the class here.
};

void g(const MemoryBlock&)
{
   cout << "In g(const MemoryBlock&)." << endl;
}

void g(MemoryBlock&&)
{
   cout << "In g(MemoryBlock&&)." << endl;
}

MemoryBlock&& f(MemoryBlock&& block)
{
   g(block);
   return move(block);
}

int main()
{
   g(f(MemoryBlock()));
}

Bu örnek aşağıdaki çıkışı oluşturur:

In g(const MemoryBlock&).
In g(MemoryBlock&&).

Örnekte, main işlevi bir rvalue değerini öğesine fgeçirir. gövdesi f , adlandırılmış parametresini bir lvalue olarak ele alır. çağrısı parametresini f g bir lvalue başvurusuna (ilk aşırı yüklenmiş sürümü g) bağlar.

  • Lvalue değerini rvalue başvurusuna dönüştürebilirsiniz.

C++ Standart Kitaplığı std::move işlevi, bir nesneyi bu nesneye rvalue başvurusuna dönüştürmenizi sağlar. Aşağıdaki örnekte gösterildiği gibi, bir lvalue'yu rvalue başvurusuna göndermek için de anahtar sözcüğünü kullanabilirsiniz static_cast :

// cast-reference.cpp
// Compile with: /EHsc
#include <iostream>
using namespace std;

// A class that contains a memory resource.
class MemoryBlock
{
   // TODO: Add resources for the class here.
};

void g(const MemoryBlock&)
{
   cout << "In g(const MemoryBlock&)." << endl;
}

void g(MemoryBlock&&)
{
   cout << "In g(MemoryBlock&&)." << endl;
}

int main()
{
   MemoryBlock block;
   g(block);
   g(static_cast<MemoryBlock&&>(block));
}

Bu örnek aşağıdaki çıkışı oluşturur:

In g(const MemoryBlock&).
In g(MemoryBlock&&).

İşlev şablonları şablon bağımsız değişken türlerini gösterir ve ardından başvuru daraltma kurallarını kullanır.

Parametrelerini başka bir işleve geçiren (veya ileten) işlev şablonu yaygın bir desendir. Rvalue başvuruları almak işlev şablonları için şablon türü kesintilerinin nasıl çalıştığını anlamak önemlidir.

İşlev bağımsız değişkeni bir rvalue ise, derleyici bağımsız değişkeni rvalue başvurusu olarak gösterir. Örneğin, türü parametresi olarak alan T&& bir işlev şablonuna türünde X bir nesneye rvalue başvurusu geçirdiğiniz varsayılır. Şablon bağımsız değişkeni kesintisiT, parametresinin türünde X&&olmasını Xsağlar. İşlev bağımsız değişkeni bir lvalue veya const lvalue ise, derleyici türünün bu türe ait bir lvalue başvurusu veya const lvalue başvurusu olduğunu gösterir.

Aşağıdaki örnekte bir yapı şablonu bildirilip çeşitli başvuru türleri için özelleştirilmiştir. İşlev, print_type_and_value parametresi olarak bir rvalue başvurusu alır ve bunu yöntemin uygun özel sürümüne S::print iletir. işlevi, main yöntemini çağırmanın S::print çeşitli yollarını gösterir.

// template-type-deduction.cpp
// Compile with: /EHsc
#include <iostream>
#include <string>
using namespace std;

template<typename T> struct S;

// The following structures specialize S by
// lvalue reference (T&), const lvalue reference (const T&),
// rvalue reference (T&&), and const rvalue reference (const T&&).
// Each structure provides a print method that prints the type of
// the structure and its parameter.

template<typename T> struct S<T&> {
   static void print(T& t)
   {
      cout << "print<T&>: " << t << endl;
   }
};

template<typename T> struct S<const T&> {
   static void print(const T& t)
   {
      cout << "print<const T&>: " << t << endl;
   }
};

template<typename T> struct S<T&&> {
   static void print(T&& t)
   {
      cout << "print<T&&>: " << t << endl;
   }
};

template<typename T> struct S<const T&&> {
   static void print(const T&& t)
   {
      cout << "print<const T&&>: " << t << endl;
   }
};

// This function forwards its parameter to a specialized
// version of the S type.
template <typename T> void print_type_and_value(T&& t)
{
   S<T&&>::print(std::forward<T>(t));
}

// This function returns the constant string "fourth".
const string fourth() { return string("fourth"); }

int main()
{
   // The following call resolves to:
   // print_type_and_value<string&>(string& && t)
   // Which collapses to:
   // print_type_and_value<string&>(string& t)
   string s1("first");
   print_type_and_value(s1);

   // The following call resolves to:
   // print_type_and_value<const string&>(const string& && t)
   // Which collapses to:
   // print_type_and_value<const string&>(const string& t)
   const string s2("second");
   print_type_and_value(s2);

   // The following call resolves to:
   // print_type_and_value<string&&>(string&& t)
   print_type_and_value(string("third"));

   // The following call resolves to:
   // print_type_and_value<const string&&>(const string&& t)
   print_type_and_value(fourth());
}

Bu örnek aşağıdaki çıkışı oluşturur:

print<T&>: first
print<const T&>: second
print<T&&>: third
print<const T&&>: fourth

İşleve yapılan her çağrıyı print_type_and_value çözümlemek için, derleyici önce şablon bağımsız değişken kesintisi yapar. Derleyici daha sonra parametre türlerini çıkarılmış şablon bağımsız değişkenleriyle değiştirdiğinde başvuru daraltma kuralları uygular. Örneğin, yerel değişkenin s1 işleve print_type_and_value geçirilmesi derleyicinin aşağıdaki işlev imzasını üretmesine neden olur:

print_type_and_value<string&>(string& && t)

Derleyici, imzayı azaltmak için başvuru daraltma kurallarını kullanır:

print_type_and_value<string&>(string& t)

İşlevin print_type_and_value bu sürümü, parametresini yöntemin doğru özelleştirilmiş sürümüne S::print iletir.

Aşağıdaki tabloda, şablon bağımsız değişken türü kesintisi için başvuru daraltma kuralları özetlenmiştir:

Genişletilmiş tür Daraltılmış tür
T& & T&
T& && T&
T&& & T&
T&& && T&&

Şablon bağımsız değişken kesintisi, mükemmel iletmenin uygulanmasında önemli bir öğedir. Mükemmel iletme bölümü, mükemmel iletmeyi daha ayrıntılı olarak açıklar.

Özet

Rvalue başvuruları, lvalue'ları rvalue'lardan ayırır. Uygulamalarınızın performansını artırmak için gereksiz bellek ayırma ve kopyalama işlemleri gereksinimini ortadan kaldırabilir. Ayrıca rastgele bağımsız değişkenleri kabul eden bir işlev yazmanıza da olanak tanır. Bu işlev, diğer işlev doğrudan çağrılmış gibi bunları başka bir işleve iletebilir.

Ayrıca bkz.

Birli işleçleri olan ifadeler
Lvalue başvuru bildirimcisi: &
Lvalues ve rvalues
Oluşturucuları taşıma ve atama işleçlerini taşıma (C++)
C++ Standart Kitaplığı