2013-10-09 10 views
35

In molti casi quando si restituisce un locale da una funzione, RVO entra in gioco. Tuttavia, ho pensato che usare esplicitamente lo std::move avrebbe dovuto forzare lo spostamento quando RVO non si verificava, ma quel RVO è ancora applicato quando possibile. Tuttavia, sembra che questo non sia il caso.Perché std :: move impedisce RVO?

#include "iostream" 

class HeavyWeight 
{ 
public: 
    HeavyWeight() 
    { 
     std::cout << "ctor" << std::endl; 
    } 

    HeavyWeight(const HeavyWeight& other) 
    { 
     std::cout << "copy" << std::endl; 
    } 

    HeavyWeight(HeavyWeight&& other) 
    { 
     std::cout << "move" << std::endl; 
    } 
}; 

HeavyWeight MakeHeavy() 
{ 
    HeavyWeight heavy; 
    return heavy; 
} 

int main() 
{ 
    auto heavy = MakeHeavy(); 
    return 0; 
} 

Ho provato questo codice con VC++ 11 e GCC 4.71, debug e di rilascio (-O2) config. Il copy ctor non viene mai chiamato. Il codice di spostamento viene chiamato solo da VC++ 11 in debug config. In realtà, sembra che tutto vada bene con questi compilatori in particolare, ma per quanto ne so, RVO è facoltativo.

Tuttavia, se uso esplicitamente move:

HeavyWeight MakeHeavy() 
{ 
    HeavyWeight heavy; 
    return std::move(heavy); 
} 

ctor Move è sempre chiamato. Quindi, cercare di renderlo "sicuro" lo rende peggiore.

Le mie domande sono:
- Perché std::move impedisce RVO?
- Quando è meglio "sperare per il meglio" e affidarsi a RVO e quando è necessario utilizzare esplicitamente lo std::move? O, in altre parole, come posso lasciare che l'ottimizzazione del compilatore faccia il suo lavoro e continui a imporre il movimento se RVO non viene applicato?

+4

Perché la gente parla ancora di "speranza per il meglio" in questi giorni? Che tipo di compilatore sta usando che ha il supporto per C++ 11 ma non può RVO correttamente? –

+1

Copia elisione (il meccanismo dietro RVO) è consentita solo in determinate condizioni rigorose. Scrivere 'std :: move' impedisce che tali condizioni vengano soddisfatte. –

+2

@KerrekSB E queste condizioni prevenute da std :: move are ...? – cdoubleplusgood

risposta

25

I casi in cui è consentita la copia e spostare elision è trovato nella sezione 12.8 § 31 dello Standard (versione N3690):

Quando determinati criteri sono soddisfatti, un'implementazione è permesso di omettere la copia/spostamento costruzione di un oggetto di classe, anche se il costruttore selezionato per l'operazione di copia/spostamento e/o il distruttore per l'oggetto hanno effetti collaterali. In tali casi, l'implementazione considera l'origine e il target dell'operazione omessa di copia/spostamento come semplicemente due modi diversi di riferirsi allo stesso oggetto e la distruzione di quell'oggetto si verifica in un secondo momento in cui i due oggetti sarebbero stati distrutto senza l'ottimizzazione. Questo l'elisione delle operazioni di copia/spostamento, chiamato copia elisione, è consentito nei seguenti casi (che possono essere combinati per eliminare più copie):

  • in una dichiarazione return in una funzione con un tipo di classe di ritorno, quando l'espressione è il nome di un oggetto automatico non volatile (diverso da una funzione o un parametro catch-clause) con lo stesso tipo cv-non qualificato del tipo restituito dalla funzione, l'operazione di copia/spostamento può essere omessa costruendo l'oggetto automatico direttamente nel valore di ritorno della funzione
  • [...]
  • quando un oggetto di classe temporaneo che non ha t stato associato a un riferimento (12.2) sarebbe stato copiato/spostato su un oggetto di classe con lo stesso tipo cv-non qualificato, l'operazione di copia/spostamento può essere omessa costruendo l'oggetto temporaneo direttamente nella destinazione della copia/mossa omessa
  • [...]

(I due casi ho lasciato fuori si riferiscono al caso di lancio e la cattura oggetti eccezione che ritengo meno importante per l'ottimizzazione.)

Quindi in una copia elisione istruzione return può avvenire solo, se l'espressione è il nome di una variabile locale. Se si scrive std::move(var), non è più il nome di una variabile. Pertanto il compilatore non può elidere la mossa, se deve conformarsi allo standard.

Stephan T. Lavavej ha parlato di questo a Going Native 2013 e ha spiegato esattamente la situazione e perché evitare std::move() qui. Inizia a guardare al minuto 38:04. Fondamentalmente, quando si restituisce una variabile locale del tipo restituito, di solito viene trattata come un valore rvalue, consentendo quindi lo spostamento per impostazione predefinita.

+0

Questa è la risposta che speravo. Non sono contento della situazione, ma capisco meglio. – cdoubleplusgood

+5

Probabilmente dovremmo risolvere è in modo che 'return std :: move' possa essere elidato. Se potessimo dire a C++ che il valore di ritorno di riferimento di una funzione è garantito uguale a un particolare valore di input di riferimento della funzione, potrebbero presentarsi alcuni risultati interessanti. (Elision da espressioni non banali, estensione a vita di un argomento di input temporaneo a una funzione senza restituire un temporaneo, a due: il secondo dei quali è IMHO più importante). – Yakk

+0

Sì, non capisco il razionale per non poterlo elidere. È solo più difficile da fare, permettendo agli scrittori di compilatori più tempo? – Adrian

13

Come posso consentire all'ottimizzatore del compilatore di eseguire il suo lavoro e applicare ancora lo spostamento se RVO non viene applicato?

Ti piace questa:

HeavyWeight MakeHeavy() 
{ 
    HeavyWeight heavy; 
    return heavy; 
} 

Trasformare il ritorno in una mossa è obbligatoria.

+0

Quindi il ritorno di un locale è garantito come una mossa nel peggiore dei casi, e il RVO si applica nel migliore dei casi? – cdoubleplusgood

+2

@cdoubleplusgood: Sì. – Xeo

+0

Credo di aver bisogno di fare un po 'di grep'ping per 'return std :: move';) – goji