Sdílet prostřednictvím


Deklarátor odkazu Rvalue: &&

Obsahuje odkaz na výraz rvalue.

Syntaxe

rvalue-reference-type-id:
type-specifier-seq&&attribute-specifier-seqopt optptr-abstract-declarator

Poznámky

Odkazy Rvalue umožňují rozlišovat lvalue od hodnoty rvalue. Odkazy Lvalue a odkazy na rvalue jsou syntakticky a sémanticky podobné, ale následují mírně odlišná pravidla. Další informace o lvalues a rvalues naleznete v tématu Lvalues a Rvalues. Další informace o odkazech lvalue naleznete v tématu Deklarátor odkazů Lvalue: &.

Následující části popisují, jak odkazy rvalue podporují implementaci sémantiky přesunutí a dokonalého přeposílání.

Přesun sémantiky

Odkazy RValue podporují implementaci sémantiky přesunutí, což může výrazně zvýšit výkon vašich aplikací. Sémantika přesunu umožňuje psát kód, který přenáší prostředky (například dynamicky přidělenou paměť) z jednoho objektu do druhého. Sémantika přesunu funguje, protože umožňuje přenos prostředků z dočasných objektů: ty, na které nejde odkazovat jinde v programu.

Chcete-li implementovat sémantiku přesunutí, obvykle zadáte konstruktor přesunutí a volitelně operátor přiřazení přesunutí (operator=) do třídy. Operace kopírování a přiřazení, jejichž zdroje jsou rvalues, pak automaticky využívají sémantiku přesunutí. Na rozdíl od výchozího konstruktoru kopírování kompilátor neposkytuje výchozí konstruktor přesunutí. Další informace o zápisu a použití konstruktoru přesunutí naleznete v tématu Přesunutí konstruktorů a operátory přiřazení přesunutí.

Můžete také přetížit běžné funkce a operátory, abyste mohli využít sémantiku přesunutí. Visual Studio 2010 zavádí sémantiku přesunu do standardní knihovny C++. Třída například string implementuje operace, které používají sémantiku přesunutí. Podívejte se na následující příklad, který zřetězí několik řetězců a vytiskne výsledek:

// 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;
}

Před sadou Visual Studio 2010 se každé volání operator+ přiděluje a vrátí nový dočasný string objekt (rvalue). operator+ nemůže připojit jeden řetězec k druhému, protože neví, jestli jsou zdrojové řetězce lvalues nebo rvalues. Pokud jsou zdrojové řetězce obě hodnoty lvalue, můžou být odkazovány jinde v programu, a proto nesmí být změněny. K převzetí operator+ hodnot rvalue můžete použít odkazy rvalue, na které nelze odkazovat jinde v programu. S touto změnou operator+ teď můžete připojit jeden řetězec k druhému. Změna významně snižuje počet přidělení dynamické paměti, které string musí třída provést. Další informace o string třídě naleznete v tématu basic_string Třída.

Sémantika přesunu také pomáhá, když kompilátor nemůže použít optimalizaci návratových hodnot (RVO) nebo pojmenovanou optimalizaci návratových hodnot (NRVO). V těchto případech kompilátor volá konstruktor přesunutí, pokud jej typ definuje.

Chcete-li lépe porozumět sémantice přesunutí, zvažte příklad vložení prvku do objektu vector . Pokud je překročena kapacita objektu vector , vector musí objekt relokovat dostatek paměti pro jeho prvky a potom zkopírovat každý prvek do jiného umístění paměti, aby se místo pro vložený prvek. Když operace vložení zkopíruje prvek, nejprve vytvoří nový prvek. Potom volá konstruktor kopírování, který zkopíruje data z předchozího prvku do nového prvku. Nakonec zničí předchozí prvek. Sémantika přesunutí umožňuje přesouvat objekty přímo, aniž byste museli provádět nákladné operace přidělení paměti a kopírování.

Pokud chcete využít sémantiku přesunutí v příkladu vector , můžete napsat konstruktor přesunutí, který přesune data z jednoho objektu do druhého.

Další informace o zavedení sémantiky přesunutí do standardní knihovny C++ v sadě Visual Studio 2010 naleznete v tématu Standardní knihovna jazyka C++.

Perfektní přeposílání

Dokonalé přeposílání snižuje potřebu přetížených funkcí a pomáhá vyhnout se problému přeposílání. K problému přeposílání může dojít při zápisu obecné funkce, která přebírá odkazy jako jeho parametry. Pokud tyto parametry předá (nebo předá) jiné funkci, například pokud přebírá parametr typu const T&, pak zavoláná funkce nemůže upravit hodnotu tohoto parametru. Pokud obecná funkce přebírá parametr typu T&, nelze funkci volat pomocí rvalue (například dočasného objektu nebo celočíselného literálu).

Obvykle je nutné, abyste tento problém vyřešili, poskytnout přetížené verze obecné funkce, které berou obě T& a const T& pro každý z jeho parametrů. V důsledku toho se počet přetížených funkcí exponenciálně zvyšuje s počtem parametrů. Odkazy Rvalue umožňují napsat jednu verzi funkce, která přijímá libovolné argumenty. Tato funkce je pak může předat jiné funkci, jako by druhá funkce byla volána přímo.

Podívejte se na následující příklad, který deklaruje čtyři typy, W, X, Ya Z. Konstruktor pro každý typ přebírá jako parametry jinou kombinaci const a nehodnotovéconst odkazy.

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

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

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

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

Předpokládejme, že chcete napsat obecnou funkci, která generuje objekty. Následující příklad ukazuje jeden ze způsobů, jak napsat tuto funkci:

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

Následující příklad ukazuje platné volání factory funkce:

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

Následující příklad však neobsahuje platné volání factory funkce. Je to proto, že factory přebírá odkazy lvalue, které jsou modifikovatelné jako parametry, ale volá se pomocí rvalue:

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

Pokud chcete tento problém vyřešit obvykle, musíte vytvořit přetíženou verzi factory funkce pro každou kombinaci A& a const A& parametry. Odkazy Rvalue umožňují napsat jednu verzi factory funkce, jak je znázorněno v následujícím příkladu:

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

V tomto příkladu factory se jako parametry funkce používají odkazy rvalue. Účelem std::forward funkce je předat parametry funkce továrny konstruktoru třídy šablony.

Následující příklad ukazuje main funkci, která používá revidovanou factory funkci k vytvoření instancí W, X, Ya Z tříd. Revidovaná factory funkce předává své parametry (lvalues nebo rvalues) do příslušného konstruktoru třídy.

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;
}

Vlastnosti odkazů rvalue

Funkci můžete přetížit tak, aby převzala odkaz na lvalue a odkaz rvalue.

Přetížením funkce, která vezme const odkaz na lvalue nebo odkaz rvalue, můžete napsat kód, který rozlišuje mezi nemodifikovatelnými objekty (lvalues) a upravitelnými dočasnými hodnotami (rvalues). Objekt lze předat funkci, která přebírá odkaz rvalue, pokud objekt není označen jako const. Následující příklad ukazuje funkci f, která je přetížena, aby vzal lvalue odkaz a rvalue odkaz. Funkce main volá f hodnoty lvalue i rvalue.

// 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());
}

Tento příklad vytvoří následující výstup:

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

V tomto příkladu první volání f předá jako argument místní proměnnou (lvalue). Druhé volání f pro předání dočasného objektu jako argumentu. Vzhledem k tomu, že dočasný objekt nelze odkazovat jinde v programu, volání vytvoří vazbu na přetíženou verzi f , která přebírá odkaz rvalue, který je volný k úpravě objektu.

Kompilátor považuje pojmenovaný odkaz rvalue za lvalue a nenázvový odkaz rvalue jako rvalue.

Funkce, které jako parametr přebírají odkaz na hodnotu rvalue, považují parametr za lvalue v těle funkce. Kompilátor zachází s pojmenovaným odkazem rvalue jako s lvalue. Je to proto, že pojmenovaný objekt může být odkazován několika částmi programu. Je nebezpečné povolit více částí programu upravovat nebo odebírat prostředky z tohoto objektu. Pokud se například více částí programu pokusí přenést prostředky ze stejného objektu, bude úspěšný pouze první přenos.

Následující příklad ukazuje funkci g, která je přetížena, aby vzal lvalue odkaz a rvalue odkaz. Funkce f přebírá jako svůj parametr odkaz rvalue (pojmenovaný odkaz rvalue) a vrátí odkaz rvalue (nenázvový odkaz rvalue). Při volání g z f, přetížení rozlišení vybere verzi g , která přebírá lvalue odkaz, protože tělo f považuje jeho parametr za lvalue. V volání g z main, přetížení rozlišení vybere verzi g , která přebírá rvalue odkaz, protože f vrátí rvalue odkaz.

// 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()));
}

Tento příklad vytvoří následující výstup:

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

V příkladu main funkce předá hodnotu rvalue .f Tělo f zpracovává jeho pojmenovaný parametr jako lvalue. Volání z f k g vytvoření vazby parametru na odkaz lvalue (první přetížená verze g).

  • Hodnotu lvalue můžete přetypovat na odkaz rvalue.

Funkce standardní knihovny std::move jazyka C++ umožňuje převést objekt na odkaz rvalue na tento objekt. Klíčové slovo můžete také použít static_cast k přetypování lvalue na odkaz rvalue, jak je znázorněno v následujícím příkladu:

// 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));
}

Tento příklad vytvoří následující výstup:

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

Šablony funkcí odvodí jejich typy argumentů šablony a pak použijí pravidla sbalení odkazů.

Šablona funkce, která předává (nebo předává) parametry jiné funkci, je běžným vzorem. Je důležité pochopit, jak funguje odpočet typu šablony pro šablony funkcí, které přebírají odkazy na hodnoty rvalue.

Pokud je argumentem funkce hodnota rvalue, kompilátor odvodí argument jako odkaz rvalue. Předpokládejme například, že předáte odkaz rvalue na objekt typu X do šablony funkce, která přebírá typ T&& jako jeho parametr. Dedukce argumentu šablony je odvozena T , takže parametr má typ X&&.X Pokud je argument funkce lvalue nebo const lvalue, kompilátor odvodí jeho typ jako odkaz lvalue nebo const lvalue odkaz daného typu.

Následující příklad deklaruje jednu šablonu struktury a pak ji specializuje na různé referenční typy. Funkce print_type_and_value přebírá jako svůj parametr odkaz na hodnotu rvalue a předává ho příslušné specializované verzi S::print metody. Funkce main ukazuje různé způsoby volání S::print metody.

// 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());
}

Tento příklad vytvoří následující výstup:

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

Při řešení každého volání print_type_and_value funkce kompilátor nejprve odpočítá argument šablony. Kompilátor pak použije pravidla sbalení odkazů, když nahradí typy parametrů odvozenými argumenty šablony. Například předání místní proměnné s1 funkci print_type_and_value způsobí, že kompilátor vytvoří následující podpis funkce:

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

Kompilátor používá referenční sbalovací pravidla k omezení podpisu:

print_type_and_value<string&>(string& t)

Tato verze print_type_and_value funkce pak předá její parametr správné specializované verzi S::print metody.

Následující tabulka shrnuje pravidla sbalení odkazů pro odpočty typu argumentu šablony:

Rozbalený typ Sbalený typ
T& & T&
T& && T&
T&& & T&
T&& && T&&

Dedukce argumentu šablony je důležitým prvkem implementace dokonalého předávání. Část Perfektní přeposílání popisuje dokonalé přeposílání podrobněji.

Shrnutí

Odkazy Rvalue rozlišují hodnoty lvalue od hodnot rvalue. Aby se zlepšil výkon vašich aplikací, můžou eliminovat potřebu nepotřebných přidělení paměti a operací kopírování. Umožňují také napsat funkci, která přijímá libovolné argumenty. Tato funkce je může předat jiné funkci, jako by druhá funkce byla volána přímo.

Viz také

Výrazy s unárními operátory
Deklarátor odkazu Lvalue: &
Hodnoty Lvalue a rvalues
Konstruktory přesunutí a operátory přiřazení přesunutí (C++)
Standardní knihovna C++