2014-11-19 9 views
12

ho una classe A che stampa un messaggio quando costruito/copiati/spostatimovimento std :: funzione in un altro std :: funzione non richiama il costruttore mossa variabili catturati

class A 
{ 
public: 
    A(std::string s) 
     :s_(s) 
    { 
     std::cout << "A constructed\n"; 
    } 
    ~A() 
    { 
     std::cout << "A destructed\n"; 
    } 
    A(const A& a) 
     :s_(a.s_) 
    { 
     std::cout << "A copy constructed\n"; 
    } 
    A(A&& a) 
     :s_(std::move(a.s_)) 
    { 
     std::cout << "A moved\n"; 
    } 
    A& operator=(const A& a) 
    { 
     s_ = a.s_; 
     std::cout << "A copy assigned\n"; 
    } 
    A& operator=(A&& a) 
    { 
     s_ = std::move(a.s_); 
     std::cout << "A move assigned\n"; 
    } 

    std::string s_; 
}; 

In main, costruisco un'istanza di A, cattura che in un lambda per valore, copiare che lambda in un std::function, e infine mossa che std::function in un'altra std::function

int main() 
{ 
    A a("hello "); 
    std::function<void()> f = [a]{ std::cout << a.s_; }; 
    std::function<void()> g(std::move(f)); 
} 

Questo stampa i seguenti

A constructed 
A copy constructed 
A copy constructed 
A destructed 
A destructed 
A destructed 

Perché il costruttore mossa di A non invocato? L'ultimo passaggio dello spostamento di f in g non ha dovuto richiamare il costruttore di spostamenti di A?

+2

coliru lo muove: http://coliru.stacked-crooked.com/a/9815dc53d6fe8a7e. Dubito che lo standard stabilisca esattamente cosa dovrebbe accadere qui, e apparentemente Microsoft ha scelto di copiare anziché spostare. – dlf

+2

Non ha nemmeno invocato il costruttore di copie. L'atto di costruire 'g' non ha chiamato nessun costruttore di' A' – tcb

+2

Hai ragione; Pensavo che il secondo "Una copia costruita" provenisse dalla costruzione di 'g', ma in realtà il primo è dalla cattura lambda e il secondo è dalla costruzione di una' funzione' fuori dal lambda. – dlf

risposta

6

Il costruttore di copie non viene chiamato precisamente perché è stato spostato il std::function. Questo perché std::function può facoltativamente memorizzare i valori catturati nell'heap e mantenere un puntatore su di essi. Pertanto, spostando la funzione è semplicemente necessario spostare quel puntatore interno. Ovviamente MSVC sceglie di archiviare le acquisizioni nell'heap e GCC, ecc. Sceglie di memorizzarle sullo stack, richiedendo così che anche i valori catturati vengano spostati.

Modifica: Grazie a Mooing Duck per indicare in un comment on the question che GCC sta anche memorizzando le acquisizioni nell'heap. La differenza effettiva sembra essere che GCC sposta le catture dal lambda allo std::function quando è costruito dal lambda.

+1

Sembra che la vera differenza qui sia che la versione di gcc/clang di 'function (lambda &&)', sposta le catture lambda, ma Microsoft no. – dlf

+1

@dlf Non riesco a incollare qui il codice della libreria standard MSVC, ma se passi questo esempio nel debugger vedrai che la riga 'std :: function g (std :: move (f));' chiama ' function (_Myt && _Right) 'which' std :: forward's '_Right' in' _Resetm (_Myt && _Right) 'che scambia semplicemente il puntatore' _Impl' con '_Right' e imposta' _Right' su null. – sjdowling

+0

Noterai inoltre che '_Resetm' contiene il controllo per vedere se i valori spostati memorizzano che è catturato localmente e in tal caso sposta le acquisizioni. – sjdowling

1

Sembra una debolezza del costruttore di spostamenti std::function di MSVC. Ho provato il tuo codice su Clang 3.3 e invoca il costruttore di mosse A.

+2

'std :: function' non sta invocando il costruttore di move in clang, gcc o MSVC. L'OP ti fuorvia. –

3

L'implementazione della libreria standard non usa la piccola ottimizzazione buffer in questo caso, quindi, la funzione f detiene un puntatore ad un mucchio stanziati regione di memoria in cui è memorizzato una copia di a. Poiché si sta spostando f in g, non vi è alcun motivo per eseguire una copia approfondita e l'implementazione può semplicemente spostare la proprietà della funzione memorizzata in f in g (come un unique_ptr).

Per quanto riguarda il motivo per cui il piccolo buffer non è qui utilizzato, questo potrebbe essere correlato al fatto che l'implementazione definisce il costruttore function mossa per essere noexcept.

Se il costruttore giocata function è noexcept, non può chiamare qualsiasi funzione che possa gettare, quindi l'attuazione semplicemente rifiutare di spostare l'oggetto (da 'buffer piccolo s alla g' la f s uno) e assegna essa sull'heap, in modo che possa semplicemente spostare un puntatore nel costruttore/compito di movimento.

Sia libstd++ e libc++ generare un costruttore di copia chiamata alla linea g = move(f) se è sufficiente aggiungere noexcept a A 's costruttore di copia.Sorprendentemente entrambi sembrano ignorare la presenza di un costruttore di spostamenti noexcept.


† Si noti che (almeno nell'ultima bozza) i mandati standard di function(function&&) di essere non noexcept, ma entrambi libstd ++ e libC++ implementa come noexcept, non riesco a controllare MSVC al momento .

Problemi correlati