2012-08-05 11 views
38

Si consideri un tipo T che supporta la semantica di spostamento predefinita. Considera anche la funzione qui sotto:Utilizzo di std :: move() quando si restituisce un valore da una funzione per evitare di copiare

T f() { 
    T t; 
    return t; 
} 

T o = f(); 

Nel vecchio C++ 03, alcuni compilatori non ottimali potrebbe chiamare il costruttore di copia due volte, una per l ' "oggetto di ritorno" e uno per o.

In C++ 11, poiché t all'interno di f() è un lvalue, questi compilatori potrebbero chiamare il costruttore di copie una volta come prima, quindi chiamare il costruttore di spostamenti per o.

È corretto affermare che l'unico modo per evitare la prima "copia extra" è spostare t al momento della restituzione?

T f() { 
    T t; 
    return std::move(t); 
} 
+0

[qui] (http://stackoverflow.com/questions/9827183/why-am-i-allowed-to-copy-unique-ptr) è una domanda simile –

+0

[FAQ relative] (http://stackoverflow.com/a/11540204/252000) – fredoverflow

risposta

42

No. Ogni volta che una variabile locale in una dichiarazione return è ammissibile per la copia elisione, si lega a un rvalue ri fe ­ ­ renza, e quindi return t; è identica a return std::move(t); nel tuo esempio, rispetto alla quale sono costruttori ammissibili.

nota tuttavia che return std::move(t);impedisce il compilatore di esercitare copia elisione, mentre return t; no, e quindi il secondo è lo stile preferito. [Grazie a @Johannes per il cor ­ rect ­.] Se si verifica l'elisione della copia, la questione di utilizzare o meno la costruzione del movimento diventa un punto controverso.

Vedere 12.8 (31, 32) nello standard.

Nota anche che se T ha una copy-accessibile ma una mossa costruttore cancellato, quindi sarà return t; non com ­ pila, poiché il costruttore spostamento deve essere considerato prima; che avrebbe dovuto dire qualcosa al ef ­ fect di return static_cast<T&>(t); per farlo funzionare:

T f() 
{ 
    T t; 
    return t;     // most likely elided entirely 
    return std::move(t);  // uses T::T(T &&) if defined; error if deleted or inaccessible 
    return static_cast<T&>(t) // uses T::T(T const &) 
} 
+0

Grazie. E il numero di chiamate al costruttore di mosse? Può essere due il numero di call per copiare i costruttori con alcuni compilatori C++ 03-complaiant? – Martin

+12

È * non * identico. È identico solo per quanto riguarda la possibilità di chiamare il costruttore di mosse. Se scrivi 'return std :: move (t);' il costruttore di move * deve * essere chiamato se il compilatore non sa cosa fa. Se scrivi 'return t;' la chiamata al costruttore di movimento può essere elidata anche se potrebbe avere effetti collaterali. –

+0

@ JohannesSchaub-litb: buon punto, lascia che lo modifichi. –

3

Ok, vorrei rilasciare un commento su questo. Questa domanda (e la risposta) mi ha fatto credere che non è necessario specificare std::move nella dichiarazione di ritorno. Tuttavia, ho pensato a una lezione diversa mentre gestivo il mio codice.

Quindi, ho una funzione (in realtà è una specializzazione) che richiede una temporanea e la restituisce. (Il modello di funzione generale fa altre cose, ma la specializzazione fa l'operazione di identità).

template<> 
struct CreateLeaf<A> 
{ 
    typedef A Leaf_t; 
    inline static 
    Leaf_t make(A &&a) { 
    return a; 
    } 
}; 

Ora, questa versione chiama il costruttore di copie di A al momento della restituzione. Se cambio l'istruzione return per

Leaf_t make(A &&a) { 
    return std::move(a); 
} 

Poi il costruttore mossa di A viene chiamato e posso fare alcune ottimizzazioni lì.

Potrebbe non corrispondere al 100% alla tua domanda. Ma è falso pensare che return std::move(..) non sia mai necessario. Ero solito pensarlo. Non più ;-)

+0

Questo è diverso dalla domanda originale. La domanda originale riguardava il ritorno x, dove x è una variabile locale. Quando x è una variabile locale, return x è migliore perché il compilatore tratterà x come valore di rvalore all'interno del reso perché sa che x è un locale. Quando x è un riferimento, il compilatore non gli darà un trattamento speciale. E poiché il tipo della variabile "a" nel tuo esempio è "A &", devi usare move per cambiarlo in "A &&". –

8

No. La procedura consigliata è direttamente return t;.

In caso classe T ha spostare costruttore non eliminati, e l'avviso t è una variabile locale che return t è idoneo per la copia elisione, muovere costrutti l'oggetto restituito, proprio come return std::move(t); fa. Tuttavia return t; è ancora idoneo a copiare/spostare elision, quindi la costruzione può essere omessa, mentre return std::move(t) costruisce sempre il valore di ritorno usando il costruttore di mosse.

Nel caso in cui il costruttore di movimento nella classe T sia eliminato ma il costruttore di copia sia disponibile, return std::move(t); non verrà compilato, mentre return t; compila ancora utilizzando il costruttore di copie. A differenza di @Kerrek menzionato, t non è associato a un riferimento di rvalue. Esiste una risoluzione di sovraccarico a due stadi per i valori di ritorno idonei per l'elisione di copia: provare prima a muovere, quindi copiare, ed entrambi possono essere spostati e copiati.

class T 
{ 
public: 
    T() = default; 
    T (T&& t) = delete; 
    T (const T& t) = default; 
}; 

T foo() 
{ 
    T t; 
    return t;     // OK: copied, possibly elided 
    return std::move(t);  // error: move constructor deleted 
    return static_cast<T&>(t); // OK: copied, never elided 
} 

Se l'espressione return è lvalue e non ammissibili per la copia elision (molto probabilmente si restituisce una variabile non locale o l'espressione lvalue) e ancora volete evitare la copia, std::move saranno utili. Ma tieni a mente che la pratica migliore è la possibilità di fare copia elision.

class T 
{ 
public: 
    T() = default; 
    T (T&& t) = default; 
    T (const T& t) = default; 
}; 

T bar(bool k) 
{ 
    T a, b; 
    return k ? a : b;   // lvalue expression, copied 
    return std::move(k ? a : b); // moved 
    if (k) 
     return a;    // moved, and possibly elided 
    else 
     return b;    // moved, and possibly elided 
} 

12.8 (32) nella norma descrive il processo.

12,8 [class.copy]

32 Quando i criteri di elisione un'operazione di copia sono soddisfatte o sarebbero soddisfatte tranne che per il fatto che l'oggetto di origine è un parametro funzione, e l'oggetto da essere copiato è designato da un lvalue, la risoluzione di sovraccarico per selezionare il costruttore per la copia viene prima eseguita come se l'oggetto fosse designato da un valore di rvalue. Se la risoluzione del sovraccarico non riesce, o se il tipo del primo parametro del costruttore selezionato non è un riferimento di valore al tipo dell'oggetto (possibilmente qualificato cv), la risoluzione di sovraccarico viene eseguita di nuovo, considerando l'oggetto come un lvalue. [Nota: questa risoluzione di sovraccarico a due stadi deve essere eseguita indipendentemente dal fatto che si verifichi l'elisione della copia. Determina il costruttore da chiamare se elision non viene eseguito e il costruttore selezionato deve essere accessibile anche se la chiamata viene eliminata. -end nota]

Problemi correlati