2010-11-10 14 views
32

Stasera ho dato un'occhiata al codice su cui ho lavorato negli ultimi giorni e ho iniziato a leggere sulla semantica del movimento, in particolare std :: move. Ho alcune domande per chiederti ai professionisti di assicurarmi che sto seguendo la strada giusta e non facendo alcuna ipotesi stupida!Questo uso corretto della semantica 'sposta' C++?

In primo luogo:

1) In origine, il mio codice ha avuto una funzione che ha restituito una grande vettore:

template<class T> class MyObject 
{ 
public: 
    std::vector<T> doSomething() const; 
    { 
     std::vector<T> theVector; 

     // produce/work with a vector right here 

     return(theVector); 
    }; // eo doSomething 
}; // eo class MyObject 

Dato "theVector" è temporanea in questo e "usa e getta", mi modificata la funzione in:

std::vector<T>&& doSomething() const; 
    { 
     std::vector<T> theVector; 

     // produce/work with a vector right here 

     return(static_cast<std::vector<T>&&>(theVector)); 
    }; // eo doSomething 

È corretto? Qualche insidia nel farlo in questo modo?

2) Ho notato in una funzione che restituisce std::string che ha chiamato automaticamente il costruttore di spostamenti. Debugging in Return of the String (grazie, Aragorn), ho notato che chiamava un costruttore di move esplicito. Perché c'è uno per la classe stringa e non vettore?

non ho dovuto apportare modifiche a questa funzione per sfruttare la semantica move:

// below, no need for std::string&& return value? 
std::string AnyConverter::toString(const boost::any& _val) const 
{ 
    string ret; 
    // convert here 
    return(ret); // No need for static_cast<std::string&&> ? 
}; // eo toString 

3) Infine, ho voluto fare alcuni test di performance, è il risultato incredibilmente-veloce Ho ottenuto std :: move semantics o il mio compilatore (VS2010) ha fatto anche qualche ottimizzazione?

(Attuazione _getMilliseconds() omessi per brevità)

std::vector<int> v; 
for(int a(0); a < 1000000; ++a) 
    v.push_back(a); 

std::vector<int> x; 
for(int a(0); a < 1000000; ++a) 
    x.push_back(a); 

    int s1 = _getMilliseconds(); 
std::vector<int> v2 = v; 
    int s2 = _getMilliseconds(); 
std::vector<int> v3 = std::move(x); 
    int s3 = _getMilliseconds(); 

    int result1 = s2 - s1; 
    int result2 = s3 - s2; 

I risultati sono stati, ovviamente, eccezionale. result1, un compito standard, ha richiesto 630ms. Il secondo risultato, era 0ms. E 'un buon test delle prestazioni di queste cose?

So che alcuni di questi sono ovvi per molti di voi, ma voglio essere sicuro di capire la semantica giusto prima di andare sul blazer del mio codice.

Grazie in anticipo!

+4

Perché stai utilizzando 'static_cast ' invece di 'std :: move'? – GManNickG

+0

@GMan - Ecco perché sto facendo queste domande. La mia motivazione originariamente proveniva da qui: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2002/n1377.htm#string Motivazione che potrei aver letto male, quindi questo post :) –

+0

Ho aggiunto un link alla mia risposta per voi da leggere se volete veramente imparare i rvalues. – GManNickG

risposta

37

Un riferimento è ancora un riferimento. Allo stesso modo non è possibile restituire un riferimento a un locale in C++ 03 (o si ottiene UB), non è possibile in C++ 0x. Finirai con un riferimento a un oggetto morto; sembra essere solo un riferimento di valore. Quindi questo è sbagliato:

std::vector<T>&& doSomething() const 
{ 
    std::vector<T> local; 

    return local; // oops 
    return std::move(local); // also oops 
} 

si dovrebbe solo fare quello che avete visto nel numero due:

// okay, return by-value 
std::vector<T> doSomething() const 
{ 
    std::vector<T> local; 

    return local; // exactly the same as: 
    return std::move(local); // move-construct value 
} 

variabili locali a una funzione sono temporanei quando si torna, quindi non c'è bisogno di cambiare uno dei tuoi codice. Il tipo restituito è la cosa responsabile dell'implementazione della semantica del movimento, non tu.

Si desidera utilizzare std::move a in modo esplicito spostare qualcosa, quando non sarebbe stato fatto normalmente, come nel test. (Che sembra andare bene, era in versione? Dovresti mostrare il contenuto del vettore, o il compilatore lo ottimizzerà.)

Se vuoi conoscere i riferimenti rvalue, read this.

+0

Il test era in modalità di debug. –

+4

@Moo: Quindi il test è stato inutile. Devi creare un profilo in Release e idealmente su un'applicazione reale. Non c'è davvero bisogno di in questo caso; spostare semantica semplicemente vincere. – GManNickG

+4

Mi sembra di ricordare che 'return std :: move (local)' può prevenire copy-elision, quindi non lo considererei lo stesso di 'return local', ma non ne sono sicuro al 100%. – fredoverflow

5

Il modo standard per spostare qualcosa è con std::move(x), non uno static_cast. AFAIK, l'ottimizzazione del valore di ritorno con nome è probabile che entri in gioco con la restituzione di un vettore in base al valore, quindi si sarebbe comportato bene prima di spostare anche la semantica.

Il test delle prestazioni è una buona illustrazione di come la semantica del movimento è buona per le prestazioni: l'assegnazione della copia deve copiare un milione di elementi e l'assegnazione del movimento in sostanza cambia solo i puntatori interni del vettore, che è una parola banale assegnazione o due, solo alcuni cicli.

14
return(theVector); 

Questo sposta già implicitamente a causa di una norma speciale linguaggio, perché theVector è un oggetto locale. Vedi la sezione 12.8 punti 34 e 35:

Quando determinati criteri sono soddisfatti, un'implementazione è consentito omettere la copia costruzione/spostamento di un oggetto classe , anche se la copia/spostamento costruttore e/o distruttore per la oggetto ha effetti collaterali. In tali casi, l'implementazione considera l'origine e la destinazione dell'operazione di copia/spostamento omessa semplicemente come due diversi modi di riferirsi allo stesso oggetto, e la distruzione di tale oggetto si verifica in un momento successivo a quando i due oggetti sarebbe stato distrutto senza l'ottimizzazione. Questa elisione copiare/spostare operazioni, chiamato elisione copia, è consentito nei seguenti casi (che possono essere combinati per eliminare le copie multiple):

- in una dichiarazione di ritorno in funzione con un tipo di classe di ritorno, quando l'espressione è il nome di un oggetto automatico non volatile con lo stesso tipo cv-non qualificato della funzione tipo di ritorno, l'operazione di copia/spostamento può essere omessa costruendo l'oggetto automatico direttamente nel valore di ritorno della funzione

[...]

Quando i criteri per l'elisione di un'operazione di copia sono soddisfatti e l'oggetto da copiare è designato da un lvalue , la risoluzione di sovraccarico per selezionare il costruttore per la copia viene eseguita come se l'oggetto fosse designato da un valore rvalue.

noti che è necessario restituire un std::vector<T> (per valore), non un std::vector<T>&& (con riferimento).

Ma perché la parentesi? return non è una funzione:

return theVector; 
+0

È principalmente dovuto all'abitudine, l'ho sempre fatto. E 'davvero così male? Mi piace un po '. anche per() non è una funzione;) (non iniziare una guerra di fuoco su di esso qui, che era principalmente ironico) –

+6

@Moo: Sì, ma 'for' * richiede * la parentesi. – fredoverflow

7

Per aggiungere alla risposta di GMan: anche se si modifica il tipo di ritorno su std::vector<T> (senza riferimenti, altrimenti si otterrà UB), la modifica dell'espressione di ritorno in "1)" non migliorerà mai le prestazioni, ma potrebbe renderlo un po 'peggiore. Come std::vector ha il costruttore di movimento e si restituisce un oggetto locale, il costruttore di copia di vector si chiamerà non, non importa che abbia scritto return theVector;, return static_cast<std::vector<T>&&>(theVector); o return std::move(theVector). Negli ultimi due casi il compilatore sarà costretto a chiamare il costruttore di movimento. Ma nel primo caso ha la libertà di ottimizzare del tutto la mossa, se può fare NRVO per quella funzione. Se NRVO non è possibile per qualche motivo, solo allora il compilatore ricorrere a chiamare il costruttore di movimento. Quindi non modificare return x; a return std::move(x); se x è un oggetto locale non statico nella funzione da cui viene restituito, altrimenti impedirai al compilatore di utilizzare un'altra opportunità di ottimizzazione.

+1

"Per aggiungere alla risposta di GMan", aggiungere un commento, non una nuova risposta. – Shoe