2015-04-18 10 views
10

Sto guardando il "Don’t Help the Compiler" parlare da STL, dove ha un esempio simile sulla slitta 26:Perché lo std :: move esplicito è necessario quando si restituisce un tipo compatibile?

struct A 
{ 
    A() = default; 
    A(const A&) { std::cout << "copied" << std::endl; } 
    A(A&&) { std::cout << "moved" << std::endl; } 
}; 

std::pair<A, A> get_pair() 
{ 
    std::pair<A, A> p; 
    return p; 
} 

std::tuple<A, A> get_tuple() 
{ 
    std::pair<A, A> p; 
    return p; 
} 

std::tuple<A, A> get_tuple_moved() 
{ 
    std::pair<A, A> p; 
    return std::move(p); 
} 

Con questo, la seguente chiamata:

get_pair(); 
get_tuple(); 
get_tuple_moved(); 

produce questo risultato:

moved 
moved 
copied 
copied 
moved 
moved 

See MCVE in action.

Il risultato di get_pair viene spostato, come previsto. Una mossa può anche essere stata completamente eliminata dall'NRVO, ma non è l'argomento della presente domanda.

Il risultato di get_tuple_moved viene anche spostato, che è esplicitamente specificato per essere tale. Tuttavia, il risultato di get_tuple è copiato, cosa che per me non è affatto ovvia.

Ho pensato che qualsiasi espressione passata all'istruzione return potrebbe essere considerata come implicita su move, poiché il compilatore sa che sta per uscire comunque. Sembra che mi sbaglio. Qualcuno può chiarire, cosa sta succedendo qui?

Vedi anche legati, ma diversa domanda: When should std::move be used on a function return value?

+0

Hai disattivato la copia elision? Inoltre, è meglio pubblicare un MCVE reale. – juanchopanza

+2

@juanchopanza In realtà è un MCVE reale, basta mettere i metodi di chiamata in 'main()'. Il comportamento per 'get_tuple' e' get_tuple_moved' è lo stesso indipendentemente da RVO, mentre 'get_pair' è influenzato. – Mikhail

+1

@Mikhail: così è in realtà _non_ un MCVE reale (in quanto richiede più lavoro di copia e incolla, anche se solo un piccolo pezzetto) :-) –

risposta

8

L'istruzione return in get_tuple() dovrebbe essere copia-inizializzato utilizzando la mossa costruttore, ma dal momento che il tipo dell'espressione di ritorno e il tipo di ritorno non si corrisponde, viene scelto invece il costruttore della copia. C'è stata una modifica in C++ 14 dove ora c'è una fase iniziale di risoluzione di sovraccarico che tratta l'istruzione return come un valore quando è semplicemente una variabile automatica dichiarata nel corpo.

Il testo in questione può essere trovato in [class.copy]/P32:

Quando i criteri per l'elisione di un'operazione di copia/spostamento sono soddisfatte, [..], o quando l'espressione in una dichiarazione di ritorno è una (id) parentesi id-espressione che nomina un oggetto con durata di archiviazione automatica dichiarata nel corpo [..], la risoluzione di sovraccarico per selezionare il costruttore per la copia viene eseguita come se l'oggetto fosse designato da un rvalue.

Quindi, in C++ 14 tutto l'output dovrebbe provenire la mossa-costruttore di A.

versioni tronco di clang e gcc già implementare questo cambiamento. Per ottenere lo stesso comportamento in modalità C++ 11 è necessario utilizzare un esplicito std :: move() nell'istruzione return.

+0

Quindi, in pratica, si tratta di un comportamento non standard, che si verifica almeno per 'gcc-4.9.2' e' Visual Studio 2015 CTP 6'? – Mikhail

+2

@Mikhail È un comportamento standard in C++ 11, ma non sono conformi allo standard più recente (C++ 14). – 0x499602D2

+0

Clang non ha ancora implementato questa modifica, ma GCC lo ha. – 0x499602D2

Problemi correlati