Perché std :: move impedisce RVO?

In molti casi, quando si restituisce un locale da una funzione, viene triggersto RVO. Tuttavia, ho pensato che l’uso esplicito di std::move avrebbe dovuto almeno forzare lo spostamento quando RVO non si verificava, ma quel RVO è ancora applicato quando ansible. Tuttavia, sembra che questo non sia il caso.

 #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 testato questo codice con VC ++ 11 e GCC 4.71, debug e release ( -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 utilizzo esplicitamente move :

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

il mittente è sempre chiamato. Quindi, cercare di renderlo “sicuro” lo rende peggiore.

Le mie domande sono:
– Perché std::move prevenire RVO?
– Quando è meglio “sperare per il meglio” e affidarsi a RVO, e quando dovrei usare esplicitamente 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?

    I casi in cui è consentito copiare e spostare elision si trovano nella sezione 12.8 §31 della norma (versione N3690):

    Quando vengono soddisfatti determinati criteri, a un’implementazione è consentito omettere la costruzione di copia / spostamento di un object di class, anche se il costruttore selezionato per l’operazione di copia / spostamento e / o il distruttore per l’object 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 object, e la distruzione di quell’object si verifica in un secondo momento in cui i due oggetti sarebbero stati distrutto senza l’ottimizzazione. Questa elisione delle operazioni di copia / spostamento, chiamata copia elisione , è consentita nelle seguenti circostanze (che possono essere combinate per eliminare più copie):

    • in una dichiarazione di return in una funzione con un tipo di ritorno di class, quando l’espressione è il nome di un object automatico non volatile (diverso da una funzione o un parametro catch-clause) con lo stesso tipo cv-non qualificato come tipo di funzione restituito, l’operazione di copia / spostamento può essere omessa costruendo l’object automatico direttamente nel valore di ritorno della funzione
    • […]
    • quando un object di class temporaneo che non è stato associato a un riferimento (12.2) verrebbe copiato / spostato in un object di class con lo stesso tipo cv-non qualificato, l’operazione di copia / spostamento può essere omessa costruendo l’object temporaneo direttamente nella destinazione della copia / mossa omessa
    • […]

    (I due casi che ho tralasciato si riferiscono al caso di lanciare e catturare oggetti di eccezione che considero meno importanti per l’ottimizzazione.)

    Quindi in una dichiarazione di ritorno, copia può essere eseguita solo se l’espressione è il nome di una variabile locale. Se scrivi 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 ne ha parlato a Going Native 2013 e ha spiegato esattamente la tua 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.

    come posso lasciare che l’ottimizzazione del compilatore faccia il suo lavoro e continui a imporre il movimento se RVO non viene applicato?

    Come questo:

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

    Trasformare il ritorno in una mossa è obbligatorio.