Dichiaratore di riferimento Rvalue: &&

Contiene un riferimento a un'espressione rvalue.

Sintassi

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

Osservazioni:

I riferimenti rvalue consentono di distinguere un lvalue da un rvalue. I riferimenti Lvalue e i riferimenti rvalue sono sintatticamente e semanticamente simili, ma seguono regole leggermente diverse. Per altre informazioni su lvalue e rvalues, vedere Lvalues e Rvalues. Per altre informazioni sui riferimenti lvalue, vedere Lvalue Reference Declarator: &.

Le sezioni seguenti descrivono come i riferimenti rvalue supportano l'implementazione della semantica di spostamento e l'inoltro perfetto.

Semantica di spostamento

I riferimenti Rvalue supportano l'implementazione della semantica di spostamento, che può aumentare significativamente le prestazioni delle applicazioni. La semantica di spostamento consente di scrivere codice per il trasferimento delle risorse (ad esempio memoria allocata in modo dinamico) da un oggetto a un altro. La semantica di spostamento funziona perché consente il trasferimento di risorse da oggetti temporanei: quelli a cui non è possibile fare riferimento altrove nel programma.

Per implementare la semantica di spostamento, in genere si fornisce un costruttore di spostamento e, facoltativamente, un operatore di assegnazione di spostamento (operator=) alla classe. Nelle operazioni di copia e assegnazione le cui origini sono valori rvalue viene automaticamente utilizzata la semantica di spostamento. A differenza del costruttore di copia predefinito, il compilatore non fornisce un costruttore di spostamento predefinito. Per altre informazioni su come scrivere e usare un costruttore di spostamento, vedere Spostare costruttori e operatori di assegnazione di spostamento.

È inoltre possibile sottoporre a overload funzioni e operatori comuni per sfruttare la semantica di spostamento. Visual Studio 2010 introduce la semantica di spostamento nella libreria standard C++. Ad esempio, la string classe implementa operazioni che usano la semantica di spostamento. Si consideri l'esempio seguente in cui vengono concatenate diverse stringhe e viene visualizzato il risultato:

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

Prima di Visual Studio 2010, ogni chiamata a operator+ alloca e restituisce un nuovo oggetto temporaneo string (rvalue). operator+ non può aggiungere una stringa all'altra perché non sa se le stringhe di origine sono lvalue o rvalue. Se le stringhe di origine sono entrambe lvalue, potrebbero essere referenziate altrove nel programma e quindi non devono essere modificate. È possibile modificare operator+ per accettare rvalue usando riferimenti rvalue, a cui non è possibile fare riferimento altrove nel programma. Con questa modifica, operator+ è ora possibile aggiungere una stringa a un'altra. La modifica riduce significativamente il numero di allocazioni di memoria dinamiche che la string classe deve eseguire. Per altre informazioni sulla string classe , vedere basic_string Classe.

La semantica di spostamento consente anche quando il compilatore non può usare l'ottimizzazione RVO (Return Value Optimization) o l'ottimizzazione dei valori restituiti denominati (NRVO). In questi casi, il compilatore chiama il costruttore di spostamento se è definito dal tipo.

Per comprendere meglio la semantica di spostamento, si consideri l'esempio dell'inserimento di un elemento in un oggetto vector. Se viene superata la capacità dell'oggetto, l'oggetto vectorvector deve riallocare memoria sufficiente per i relativi elementi e quindi copiare ogni elemento in un'altra posizione di memoria per liberare spazio per l'elemento inserito. Quando un'operazione di inserimento copia un elemento, crea prima di tutto un nuovo elemento. Chiama quindi il costruttore di copia per copiare i dati dall'elemento precedente al nuovo elemento. Infine, elimina definitivamente l'elemento precedente. La semantica di spostamento consente di spostare gli oggetti direttamente senza dover eseguire operazioni di allocazione di memoria e copia costose.

Per sfruttare i vantaggi della semantica di spostamento nell'esempio vector, è possibile scrivere un costruttore di spostamento per spostare i dati da un oggetto a un altro.

Per altre informazioni sull'introduzione della semantica di spostamento nella libreria standard C++ in Visual Studio 2010, vedere Libreria standard C++.

Inoltro perfetto

L'inoltro perfetto riduce la necessità delle funzioni sottoposte a overload e consente di evitare il problema di inoltro. Il problema di inoltro può verificarsi quando si scrive una funzione generica che accetta riferimenti come parametri. Se passa (o inoltra) questi parametri a un'altra funzione, ad esempio, se accetta un parametro di tipo const T&, la funzione chiamata non può modificare il valore di tale parametro. Se la funzione generica accetta un parametro di tipo T&, la funzione non può essere chiamata usando un rvalue ,ad esempio un oggetto temporaneo o un valore letterale integer.

In genere, per risolvere questo problema, è necessario fornire versioni sottoposte a overload della funzione generica che accettano T& e const T& per ogni parametro. Di conseguenza, il numero delle funzioni sottoposte a overload aumenta in modo esponenziale con il numero di parametri. I riferimenti Rvalue consentono di scrivere una versione di una funzione che accetta argomenti arbitrari. Tale funzione può quindi inoltrarle a un'altra funzione come se l'altra funzione fosse stata chiamata direttamente.

Si consideri l'esempio seguente in cui vengono dichiarati quattro tipi, W, X, Y e Z. Il costruttore per ogni tipo accetta una combinazione diversa di const e riferimenti nonconst lvalue come parametri.

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

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

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

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

Si supponga di voler scrivere una funzione generica che genera oggetti. Nell'esempio riportato di seguito viene illustrato un modo per la scrittura di questa funzione:

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

Nell'esempio seguente viene illustrata una chiamata valida alla funzione factory:

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

Tuttavia, l'esempio seguente non contiene una chiamata valida alla factory funzione. factory Poiché accetta riferimenti lvalue modificabili come parametri, ma viene chiamato usando rvalues:

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

In genere, per risolvere questo problema, è necessario creare una versione sottoposta a overload della funzione factory per ogni combinazione di A& e parametri const A&. I riferimenti rvalue consentono di scrivere una versione della funzione factory, come illustrato nell'esempio seguente:

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

In questo esempio vengono utilizzati i riferimenti rvalue come parametri della funzione factory. Lo scopo della std::forward funzione è inoltrare i parametri della funzione factory al costruttore della classe modello.

Nell'esempio seguente viene illustrata la funzione main che utilizza la funzione factory modificata per creare istanze delle classi W, X, Y e Z. La funzione factory modificata inoltra i relativi parametri (valori lvalue o rvalue) al costruttore della classe appropriato.

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

Proprietà dei riferimenti rvalue

È possibile eseguire l'overload di una funzione per accettare un riferimento lvalue e un riferimento rvalue.

Eseguendo l'overload di una funzione per accettare un riferimento lvalue o un const riferimento rvalue, è possibile scrivere codice che distingue tra oggetti non modificabili (lvalue) e valori temporanei modificabili (rvalue). È possibile passare un oggetto a una funzione che accetta un riferimento rvalue a meno che l'oggetto non sia contrassegnato come const. Nell'esempio seguente viene illustrata la funzione f, che viene sottoposta a overload per accettare un riferimento lvalue e un riferimento rvalue. La funzione main chiama f con i valori lvalue e un valore 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());
}

Nell'esempio viene prodotto l'output seguente:

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

In questo esempio, la prima chiamata a f passa una variabile locale (un lvalue) come argomento. La seconda chiamata a f passa un oggetto temporaneo come argomento. Poiché l'oggetto temporaneo non può essere fatto riferimento altrove nel programma, la chiamata viene associata alla versione di overload di f che accetta un riferimento rvalue, che è libero di modificare l'oggetto.

Il compilatore considera un riferimento rvalue denominato come riferimento lvalue e un riferimento rvalue senza nome come rvalue.

Le funzioni che accettano un riferimento rvalue come parametro considerano il parametro come lvalue nel corpo della funzione. Il compilatore considera un riferimento rvalue denominato come lvalue. È perché un oggetto denominato può essere fatto riferimento da diverse parti di un programma. È pericoloso consentire a più parti di un programma di modificare o rimuovere risorse da tale oggetto. Ad esempio, se più parti di un programma tentano di trasferire risorse dallo stesso oggetto, solo il primo trasferimento ha esito positivo.

Nell'esempio seguente viene illustrata la funzione g, che viene sottoposta a overload per accettare un riferimento lvalue e un riferimento rvalue. La funzione f accetta un riferimento rvalue come parametro (un riferimento rvalue denominato) e restituisce un riferimento rvalue (un riferimento rvalue senza nome). Nella chiamata a g da f, la risoluzione dell'overload seleziona la versione g che accetta un riferimento lvalue poiché il corpo di f considera il parametro come lvalue. Nella chiamata a g da main, la risoluzione dell'overload seleziona la versione di g che accetta un riferimento rvalue poiché f restituisce un riferimento 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 move(block);
}

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

Nell'esempio viene prodotto l'output seguente:

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

Nell'esempio la main funzione passa un rvalue a f. Il corpo di f tratta il parametro denominato come un lvalue. La chiamata da f a g associa il parametro a un riferimento lvalue (la prima versione sottoposta a overload di g).

  • È possibile eseguire il cast di un lvalue in un riferimento rvalue.

La funzione libreria std::move standard C++ consente di convertire un oggetto in un riferimento rvalue a tale oggetto. È anche possibile usare la parola chiave per eseguire il static_cast cast di un lvalue in un riferimento rvalue, come illustrato nell'esempio seguente:

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

Nell'esempio viene prodotto l'output seguente:

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

I modelli di funzione deducono i tipi di argomento modello e quindi usano regole di compressione dei riferimenti.

Un modello di funzione che passa (o inoltra) i relativi parametri a un'altra funzione è un modello comune. È importante comprendere il funzionamento della deduzione del tipo di modello per i modelli di funzione che accettano riferimenti rvalue.

Se l'argomento della funzione è un rvalue, il compilatore deduce che l'argomento sia un riferimento rvalue. Si supponga, ad esempio, di passare un riferimento rvalue a un oggetto di tipo X a un modello di funzione che accetta il tipo T&& come parametro. La deduzione dell'argomento di modello deduce T essere X, quindi il parametro ha il tipo X&&. Se l'argomento della funzione è un lvalue o const lvalue, il compilatore deduce il relativo tipo come riferimento lvalue o const riferimento lvalue di tale tipo.

Nell'esempio seguente viene dichiarato un modello di struttura, quindi ne viene definita la specializzazione per diversi tipi di riferimento. La funzione print_type_and_value accetta un riferimento rvalue come parametro e lo inoltra alla versione specializzata appropriata del metodo S::print. La funzione main illustra i vari modi di chiamare il metodo 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());
}

Nell'esempio viene prodotto l'output seguente:

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

Per risolvere ogni chiamata alla print_type_and_value funzione, il compilatore esegue prima la deduzione dell'argomento del modello. Il compilatore applica quindi regole di compressione dei riferimenti quando sostituisce i tipi di parametro con gli argomenti modello dedotti. Ad esempio, passando la variabile locale s1 alla funzione print_type_and_value il compilatore genererà la seguente firma funzione:

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

Il compilatore usa regole di compressione dei riferimenti per ridurre la firma:

print_type_and_value<string&>(string& t)

Questa versione della funzione print_type_and_value inoltra quindi il relativo parametro alla versione specializzata corretta del metodo S::print.

Nella tabella seguente vengono riepilogate le regole di compressione dei riferimenti per la deduzione del tipo di argomento del modello:

Tipo espanso Tipo compresso
T& & T&
T& && T&
T&& & T&
T&& && T&&

La deduzione dell'argomento di modello è un elemento importante dell'implementazione dell'inoltro perfetto. La sezione Inoltro perfetto descrive in modo più dettagliato l'inoltro perfetto.

Riepilogo

I riferimenti rvalue distinguono gli lvalue dagli rvalue. Per migliorare le prestazioni delle applicazioni, possono eliminare la necessità di allocazioni di memoria e operazioni di copia non necessarie. Consentono inoltre di scrivere una funzione che accetta argomenti arbitrari. Tale funzione può inoltrarle a un'altra funzione come se l'altra funzione fosse stata chiamata direttamente.

Vedi anche

Espressioni con operatori unari
Dichiaratore di riferimento Lvalue: &
Lvalue e rvalues
Spostare costruttori e operatori di assegnazione di spostamento (C++)
Libreria standard C++