Sdílet prostřednictvím


Deklarátor odkazu hodnoty R: &&

Obsahuje odkaz na výraz rvalue.

type-id && cast-expression

Poznámky

Odkazy rvalue umožňují odlišit lvalue od rvalue.Odkazy lvalue a odkazy rvalue jsou syntakticky a sémanticky podobné, ale řídí se poněkud odlišnými pravidly.Další informace o hodnotách lvalue a rvalue najdete v tématu Hodnoty Lvalue a Rvalue.Další informace o odkazování se na hodnotu lvalue naleznete v tématu Deklarátor odkazu Lvalue: &.

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

Přesunutí sémantik

Odkazy rvalue podporují implementaci sémantiky přesunutí, která může výrazně zvýšit výkon aplikace.Přesunutí sémantik umožňuje napsat kód, který převede prostředky (jako je dynamicky přidělená paměť) z jednoho objektu na jiný.Přesunutí sémantik funguje, protože umožňuje převedení prostředků z dočasných objektů, na které nelze odkazovat jinde v programu.

K implementaci sémantiky přesunutí obvykle poskytujete konstruktor přesunutí a volitelně operátor přiřazení přesunutí (operator=) do vaší třídy.Operace kopírování a přiřazení, jejichž zdroje jsou hodnoty rvalues pak automaticky využijí sémantiky přesunutí.Na rozdíl od konstruktoru výchozí kopie kompilátor neposkytuje výchozí konstruktor pro přesunutí.Další informace o zápisu přesunu konstruktoru a jeho použití v aplikaci naleznete v tématu Postupy: Zápis konstruktoru přesunu.

Můžete také použít běžné funkce přetížení a operátory k využití výhod přesunutí sémantiky.Visual C++ 2010 představuje přesunutí sémantiky do knihovny STL (Standard Template Library).Například třída string implementuje operace, které provádějí přesunutí sémantiky.Zvažte 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 Visual C++ 2010 každé volání pro operator+ přiděluje a vrací nový dočasný objekt string (rvalue).operator+ nedokáže připojit jeden řetězec k druhému, protože neví, zda jsou zdrojové řetězce lvalue nebo rvalue.Pokud jsou oba zdrojové řetězce hodnotami lvalues, mohou být odkazovány kdekoli v programu a nesmí být proto změněny.Pomocí odkazů rvalue operator+ může být změněn, aby převzal rvalues, které nelze odkazovat kdekoli v programu.Proto operator+ může nyní přidat jeden řetězec do jiného.To může výrazně snížit počet přidělení dynamické paměti, které musí třída string provést.Další informace o třídě string naleznete v tématu basic_string – třída.

Přesunutí sémantik také pomáhá, když kompilátor nemůže použít RVO nebo NRVO.V těchto případech kompilátor volá konstruktor přesunu, pokud jej definuje typ.Další informace o optimalizaci vrácení hodnoty s názvem naleznete v tématu Vrácení pojmenované optimalizace hodnot v aplikaci Visual C++ 2005.

Abyste lépe pochopili sémantiku přesunutí, zvažte příklad vložení elementu do objektu vector.Pokud kapacita objektu vector je překročena, objekt vector musí znovu přidělit paměti její elementy a poté každý element zkopírovat do jiného umístění v paměti a uvolnit tak prostor pro vložený element.Když operace vložení zkopíruje element, vytvoří nový element, volá konstruktor ke kopírování dat z předchozí ho elementu do nového, a potom zničí předchozí prvek.Přesunutí sémantik umožňuje přesunout objekty přímo bez nutnosti provádět náročné přidělení paměti a operace kopírování.

Chcete-li využít výhod sémantiky přesunutí v příkladu vector, můžete zapsat konstruktor přesunutí přesun dat z jednoho objektu do druhého.

Další informace o úvodu sémantiky přesunutí do STL v Visual C++ 2010 naleznete v tématu Standardní knihovna C++ – referenční dokumentace.

Perfektní přesměrování

Perfektní přesměrování snižuje potřebu přetížených funkcí a pomáhá vyhnout se problému s předáváním.Problém s předáváním může vzniknout při psaní obecné funkce, která přebírá odkazy jako svoje parametry a odesílá (nebo předává) tyto parametry jiné funkci.Například, pokud obecná funkce má parametr typu const T& tak volaná funkce nemůže změnit hodnotu tohoto parametru.Pokud obecná funkce má parametr typu T&, pak tuto funkci nelze volat pomocí rvalue (například dočasný objekt nebo literál celého čísla).

Obvykle pro vyřešení tohoto problému je nutné zadat přetížené verze obecných funkcí, které využívají T& a const T& pro každý ze svých parametrů.V důsledku toho se počet přetížených funkcí zvyšuje exponenciálně s počtem parametrů.Odkazy rvalue umožňují také napsat jednu verzi funkce, která přijímá libovolný argument, a předá je do jiné funkce, jako kdyby byla jiná funkce přímo volána.

Vezměte následující příklad, který deklaruje čtyři typy W, X, Y a Z.Konstruktor pro každý typ má jinou kombinaci odkazů na hodnoty lvalue const a non-const jako svoje parametry.

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á vytváří objekty.Následující příklad ukazuje jeden ze způsobů jak napsat 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í funkce factory:

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

V následujícím příkladu však neobsahuje platné volání funkce factory, protože factory bere odkazy lvalue, které lze měnit její parametry, ale je volána pomocí rvalues:

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

Obvykle pro vyřešení tohoto problému je nutné vytvořit přetíženou verzi funkce factory pro každou kombinaci parametrů A& a const A&.Odkazy rvalue umožňují napsat jednu verzi funkce factory, 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));
}

Tento příklad používá odkazy rvalue jako parametry funkce factory.Účelem funkce std::forward je předat parametry tovární funkce konstruktoru třídy šablony.

Následující příklad ukazuje funkci main, která používá revidovanou funkci factory pro vytvoření instancí tříd W, X, Y a Z.Revidovaná funkce factory předává své parametry (lvalue nebo rvalue) do odpovídající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;
}

Další vlastnosti odkazů Rvalue

Můžete použít přetížení funkce k převzetí reference na lvalue a rvalue.

Přetížením funkce pro převzetí odkaz const lvalue nebo odkazu rvalue můžete napsat kód, který rozlišuje mezi neupravitelnými objekty (lvalues) a změnitelnými dočasnými hodnotami (rvalues).Objekt můžete 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 pro převzetí odkazu na hodnotu lvalue a rvalue.FUnkce main volá f s hodnotami lvalues 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 cannot 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 cannot modify the parameter.
In f(MemoryBlock&&). This version can modify the parameter.

V tomto příkladu první volání do f předává místní proměnnou (lvalue) jako svůj argument.Druhé volání metody f předává jako argument dočasný objekt.Vzhledem k tomu, že dočasný objekt nelze odkazovat kdekoli v programu, volání vytvoří vazbu přetížené verze f, která má odkaz rvalue, který umožňuje volné úpravy objektu.

Kompilátor považuje pojmenované odkazy rvalue za hodnoty lvalue a nepojmenované odkazy rvalue za hodnoty rvalue.

Při zápisu funkce, která jako svůj parametr přebírá referenci rvalue, je tento parametr považován za lvalue v těle funkce.Kompilátor považuje pojmenované odkazy rvalue za hodnoty lvalue, protože na pojmenovaný objekt lze umístit odkaz z několika částí programu. Bylo by nebezpečné povolit více částem programu změnit nebo odebrat prostředky z tohoto objektu.Například pokud se více částí programu pokusí převést prostředky ze stejného objektu, pouze první část úspěšně převede prostředek.

Následující příklad ukazuje funkci g, která je přetížena pro převzetí odkazu na hodnotu lvalue a rvalue.Funkce f použije odkaz rvalue jako svůj parametr (pojmenovaný odkaz rvalue) a vrátí odkaz rvalue (nepojmenovaný odkaz rvalue).Při volání g z f řešení přetížení vybere verzi g, která má odkaz na lvalue, protože tělo f zachází se svým parametrem jako s hodnotou lvalue.Při volání do g z main řešení přetížení vybere verzi g, která má odkaz na rvalue, protože f vrací odkaz rvalue.

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

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

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

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

V tomto příkladu funkce main předává rvalue do f.Text f zachází se svým pojmenovaným parametrem jako s hodnotou lvalue.Volání z f do g váže parametr na odkaz lvalue (první přetížená verze g).

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

Funkce STL std::move umožňuje převést objekt na odkaz rvalue na tento objekt.Alternativně lze použít klíčové slovo static_cast pro převedení hodnoty 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í odvozují své typy argumentů šablon a poté použijí odkazy pravidel pro sbalení.

Je běžné vytvořit šablonu funkce, která předává (nebo přesměruje) své parametry do jiné funkce.Je důležité pochopit, jak funguje odpočet typu šablony pro šablony funkce, na které hodnota rvalue odkazuje.

Pokud je argumentem funkce rvalue, kompilátor odvodí, že argument bude odkaz rvalue.Například pokud předáte odkaz rvalue objektu typu X do šablony funkce, která má typ T&& jako svůj parametr, šablona dedukce argumentu určí, že T má být X.Proto má parametr typ X&&.Pokud je argumentem funkce lvalue nebo const lvalue, kompilátor odvodí jeho typ, který má být odkaz na lvalue nebo odkaz const lvalue tohoto typu.

Následující příklad deklaruje jednu šablonu struktury a poté ji specializuje pro různé typy odkazů.Funkce print_type_and_value bere odkaz rvalue jako svůj parametr a předá jej do příslušné speciální verze metody S::print.Funkce main ukazuje různé způsoby volání metody S::print.

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

K vyřešení každého volání funkce print_type_and_value kompilátor provádí nejprve odvození argumentu šablony.Kompilátor poté při nahrazení odvozených argumentů pro typy parametrů použije pravidla pro sbalování odkazu.Například předávání místní proměnné s1 k 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á pravidla pro sbalování odkazu ke redukci podpisu následujícím způsobem:

print_type_and_value<string&>(string& t)

Tato verze funkce print_type_and_value následně předá svůj parametr do správné specializované verze metody S::print.

Následující tabulka shrnuje pravidla sbalení odkazu pro srážku typu argumentu šablony:

Rozšířený typ

Sbalený typ

T& &

T&

T& &&

T&

T&& &

T&

T&& &&

T&&

Odečtení argumentu šablony je důležitým prvkem při provádění perfektního přesměrování.Oddíl Dokonalé předávání popsaný dříve v tomto tématu podrobněji popisuje dokonalé předávání.

SHRNUTÍ

Odkazy rvalue rozlišují mezi hodnotami lvalue a rvalue.Mohou pomoci zvýšit výkon vašich aplikací, vyloučením potřeby zbytečného přidělení a operací kopírování.Umožňují také napsat jednu verzi funkce, která přijímá libovolný argument, a předá je do jiné funkce, jako kdyby byla jiná funkce přímo volána.

Viz také

Úkoly

Postupy: Zápis konstruktoru přesunu

Referenční dokumentace

Výrazy s unárními operátory

Deklarátor odkazu Lvalue: &

Hodnoty Lvalue a Rvalue

move

forward

Další zdroje

Standardní knihovna C++ – referenční dokumentace