2012-10-27 14 views
14

Supponiamo di avere questa classe:C++ 11 mossa costruttore non chiama, costruttore predefinito preferito

class X { 
public: 
    explicit X (char* c) { cout<<"ctor"<<endl; init(c); }; 
    X (X& lv) { cout<<"copy"<<endl; init(lv.c_); }; 
    X (X&& rv) { cout<<"move"<<endl; c_ = rv.c_; rv.c_ = nullptr; }; 

    const char* c() { return c_; }; 

private: 
    void init(char *c) { c_ = new char[strlen(c)+1]; strcpy(c_, c); }; 
    char* c_; 

}; 

e questo utilizzo di esempio:

X x("test"); 
cout << x.c() << endl; 
X y(x); 
cout << y.c() << endl; 
X z(X("test")); 
cout << z.c() << endl; 

Il risultato è:

ctor 
test 
copy 
test 
ctor <-- why not move? 
test 

Sto usando VS2010 con le impostazioni predefinite. Mi aspetto che l'ultimo oggetto (z) venga spostato, ma non lo è! Se utilizzo X z(move(X("test")));, le ultime righe dell'output sono ctor move test, come previsto. È un caso di (N) RVO?

Q: il chiamante deve essere chiamato secondo lo standard? Se è così, perché non si chiama?

+2

E 'copia elisione. Se l'elisione della copia falliva, si verificherà una mossa. Perché il titolo del tuo post dice "costruttore preferito predefinito"? Non viene chiamato alcun costruttore predefinito e al posto del costruttore di movimento non viene preferito nulla. Viene completamente eliminato. –

+0

Questo codice non dovrebbe essere compilato da C++ 11; una stringa letterale non può essere convertita implicitamente in 'char *' non-const. –

risposta

19

Quello che state vedendo è copy elision, che consente al compilatore di costruire direttamente un oggetto temporaneo in una destinazione che deve essere copiato/spostato e quindi elidere una copia (o spostare) coppia costruttore/distruttore. Le situazioni in cui è consentito il compilatore di applicare copia elision sono specificate nella §12.8.32 del C++ 11 norma:

Quando determinati criteri sono soddisfatti, un'implementazione è consentito omettere copia/spostamento costruzione di un oggetto di classe, anche se la funzione di copia/spostamento costruttore e/o distruttore per l'oggetto hanno fughe laterali. In tali casi , l'implementazione considera l'origine e la destinazione dell'operazione di copia/spostamento omessa come due modi diversi di riferirsi allo stesso oggetto e la distruzione di tale oggetto si verifica in un secondo momento di le volte in cui i due gli oggetti sarebbero stati distrutti senza l'ottimizzazione . Questa elisione copia/spostare operazioni, chiamato copia elisione, è consentito nei seguenti circostanze (che magari abbinato ad eliminare più copie):

  • 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
    stesso cv-unquali tipo fi cata come tipo di funzione di ritorno, l'operazione
    copia/spostamento può essere omesso costruendo automatico
    oggetto direttamente nel valore di ritorno della funzione
  • in un lancio-ex pression, quando l'operando è il nome di un oggetto automatico non volatile il cui ambito non si estende oltre alla fine del try-block di chiusura più interno (se ce n'è uno), l'operazione di copia/spostamento dall'operando all'eccezione oggetto (15.1) può essere omesso costruendo l'oggetto automatico direttamente nell'oggetto
  • oggetto di eccezione direttamente quando un oggetto di classe temporaneo non è stato associato a un riferimento (12.2) verrebbe copiato/spostato a un oggetto di classe con lui stesso cv-unquali tipo fi cato, l'operazione di copia/spostamento può essere omesso dal costruire l'oggetto temporaneo direttamente nella destinazione del
    copia omessa/spostare
  • quando la dichiarazione di eccezione di un gestore di eccezioni (clausola 15) dichiara un oggetto dello stesso tipo (eccetto per cv-quali fi cation) come
    l'oggetto di eccezione (15.1), l'operazione di copia/spostamento può essere omessa
    bytreatingthe exception-declaration come alias per l'eccezione oggetto se il significato del programma sarà invariato tranne per l'esecuzione di costruttori e distruttori per l'oggetto dichiarato da
    la dichiarazione di eccezione.
+1

Puoi fornire un semplice esempio che non usa 'std :: move' che costringerebbe il compilatore a usare il costruttore di movimento? – emesx

+0

@elmes: Perché dovresti chiamare il costruttore di movimento, quando non è necessario farlo? – Grizzly

+0

Mi piacerebbe vedere qualsiasi esempio di usarlo con la classe 'X', solo per credere che abbia senso nel definirne uno. – emesx

0

Si sta chiamando X'schar* costruttore X("test") esplicitamente.

Pertanto la fase di stampa ctor

+1

E poi chiamare il costruttore di spostamenti di 'X' quando si dichiara' z'. – Xeo

3

L'ctor uscita si ottiene in linea terzo codice è per la costruzione dell'oggetto temporaneo. Dopodiché, in effetti, il temporaneo viene spostato nella nuova variabile z. In una situazione del genere, il compilatore può scegliere di eliminare la copia/mossa e sembra che sia ciò che ha fatto.

Lo standard:

(§12.8/31) Quando determinati criteri sono soddisfatti, un'implementazione è consentito omettere la copia costruzione/spostamento di un oggetto classe, anche se il costruttore di copia/spostamento e/o distruttore per l'oggetto hanno effetti collaterali. [...] Questa elisione delle operazioni di copia/spostamento, chiamata copia elisione, è consentita nelle seguenti circostanze (che possono essere combinate per eliminare più copie):
[...]
- quando un oggetto di classe temporaneo che non è stato associato a un riferimento (12.2) sarebbe stato copiato/spostato in un oggetto di classe con lo stesso tipo cv-nonqualificato, l'operazione di copia/spostamento può essere omessa costruendo l'oggetto temporaneo direttamente nella destinazione della copia/mossa omessa
[...]

Una condizione importante è che l'oggetto di origine e la destinazione sono dello stesso tipo (a parte cv-qualificazione, cose cioè come const).

Pertanto, in un modo è possibile forzare il costruttore mossa da chiamare è quello di combinare l'inizializzazione oggetto con conversione di tipo implicita:

#include <iostream> 

struct B 
{}; 

struct A 
{ 
    A() {} 
    A(A&& a) { 
    std::cout << "move" << std::endl; 
    } 
    A(B&& b) { 
    std::cout << "move from B" << std::endl; 
    } 
}; 


int main() 
{ 
    A a1 = A(); // move elided 
    A a2 = B(); // move not elided because of type conversion 
    return 0; 
} 
+0

È possibile che 'A :: A (B && b)' sia considerato come _move constructor_? N3936 draft (12.8.3) dice: "Un costruttore non-template per la classe X è un costruttore di move se il suo primo parametro è di tipo X &&, const X &&, volatile X &&, const volatile X &&, e non ci sono altri parametri o altrimenti tutti gli altri parametri hanno argomenti predefiniti " –

Problemi correlati