2012-06-08 7 views
13

Considerare:Devo std: spostare un shared_ptr in un costruttore di move?

#include <cstdlib> 
#include <memory> 
#include <string> 
#include <vector> 
#include <algorithm> 
#include <iterator> 
using namespace std; 

class Gizmo 
{ 
public: 
    Gizmo() : foo_(shared_ptr<string>(new string("bar"))) {}; 
    Gizmo(Gizmo&& rhs); // Implemented Below 

private: 
    shared_ptr<string> foo_; 
}; 

/* 
// doesn't use std::move 
Gizmo::Gizmo(Gizmo&& rhs) 
: foo_(rhs.foo_) 
{ 
} 
*/ 


// Does use std::move 
Gizmo::Gizmo(Gizmo&& rhs) 
: foo_(std::move(rhs.foo_)) 
{ 
} 

int main() 
{ 
    typedef vector<Gizmo> Gizmos; 
    Gizmos gizmos; 
    generate_n(back_inserter(gizmos), 10000, []() -> Gizmo 
    { 
     Gizmo ret; 
     return ret; 
    }); 

    random_shuffle(gizmos.begin(), gizmos.end()); 

} 

Nel codice sopra, ci sono due versioni di Gizmo::Gizmo(Gizmo&&) - uno usa std::move realmente mossa il shared_ptr, e l'altro appena copia il shared_ptr.

Entrambe le versioni sembrano funzionare in superficie. Una differenza (l'unica differenza che posso vedere) è nella versione non move il conteggio dei riferimenti dello shared_ptr è temporaneamente aumentato, ma solo brevemente.

Vorrei normalmente andare avanti e move il shared_ptr, ma solo per essere chiari e coerenti nel mio codice. Mi manca una considerazione qui? Dovrei preferire una versione rispetto all'altra per una ragione tecnica ?

+2

Lo spostamento in un costruttore di movimento è almeno semanticamente coerente ... – ildjarn

+1

Perché mantieni la stringa in un shared_ptr? Un shared_ptr come variabile membro è il più delle volte un segno di cattiva progettazione. –

+1

Lo spostamento in un costruttore di movimento è in linea con ciò che il compilatore genererebbe automaticamente. –

risposta

16

Il problema principale qui non è la piccola differenza di prestazioni dovuta all'incremento e decremento atomico in più in shared_ptr ma che la semantica dell'operazione è incoerente a meno che non si esegua uno spostamento.

Mentre il presupposto è che il conteggio dei riferimenti dello shared_ptr sarà solo temporaneo, non esiste tale garanzia nella lingua. L'oggetto sorgente da cui ti stai muovendo può essere temporaneo, ma potrebbe anche avere una durata molto più lunga. Potrebbe essere una variabile denominata che è stata fusa a un rvalue riferimento (diciamo std::move(var)), nel qual caso, non spostando dal shared_ptr si sta ancora mantenendo condiviso proprietà con la fonte del movimento, e se la destinazione shared_ptr ha un ambito più piccolo, quindi la durata dell'oggetto appuntito sarà estesa senza necessità.

+0

Mi chiedo quando uso le mosse, in che misura dovremmo pensare a noi stessi, "questa mossa potrebbe degradarsi in una copia"? Ovviamente lo facciamo quando scriviamo il codice del template per un 'T' arbitrario 'MoveConstructible', dal momento che non ha bisogno di avere un costruttore di movimento, per non parlare di uno che modifica la sorgente. Inoltre, ovviamente, è un QoI scadente se un oggetto spostato da contiene risorse inutilmente, quindi se 'Gizmo' ha un costruttore di mosse allora dovrebbe essere buono. Ma penso che sia una questione di convenzione e di stile di codifica come possiamo avere il diritto di essere quando non lo è. –

+0

+1, questo è quello che stavo ottenendo nel mio commento sulla risposta di James. Ben messo. – ildjarn

+0

Per un altro esempio, un'implementazione valida di un'assegnazione di movimento è chiamare 'swap'. Lo stato dell'oggetto spostato da non è specificato e quindi, in particolare, è consentito essere lo stato dell'oggetto spostato. Ma saresti un po 'seccato che le risorse dell'oggetto * spostato verso * si aggirino indefinitamente. –

11

L'uso di move è preferibile: dovrebbe essere più efficiente di una copia perché non richiede l'incremento e il decremento atomico extra del conteggio dei riferimenti.

+0

Ci sono anche differenze semantiche: ci si aspetterebbe che uno spostato da 'Gizmo' mantenga vivo il suo stato condiviso interno? Personalmente lo troverei sorprendente e mi aspetto che un oggetto spostato da rilascia tutto lo stato condiviso. – ildjarn

+1

@ildjarn: Sono d'accordo, anche se non influenzerà la correttezza del programma in entrambi i modi: il spostato da oggetto sarà ancora distruttibile e assegnabile a. –

13

Ho svalutato la risposta di James McNellis. Vorrei fare un commento sulla sua risposta ma il mio commento non si adatta al formato dei commenti. Quindi lo sto mettendo qui.

Un modo divertente per misurare l'impatto delle prestazioni dello spostamento di uno shared_ptr rispetto a quello di uno è quello di utilizzare qualcosa come vector<shared_ptr<T>> per spostare o copiare un sacco di loro e il tempo. La maggior parte dei compilatori ha un modo per attivare/disattivare la semantica del movimento specificando la modalità lingua (ad esempio -std = C++ 03 o -std = C++ 11).

Ecco codice che ho appena provato a -O3:

#include <chrono> 
#include <memory> 
#include <vector> 
#include <iostream> 

int main() 
{ 
    std::vector<std::shared_ptr<int> > v(10000, std::shared_ptr<int>(new int(3))); 
    typedef std::chrono::high_resolution_clock Clock; 
    typedef Clock::time_point time_point; 
    typedef std::chrono::duration<double, std::micro> us; 
    time_point t0 = Clock::now(); 
    v.erase(v.begin()); 
    time_point t1 = Clock::now(); 
    std::cout << us(t1-t0).count() << "\u00B5s\n"; 
} 

Utilizzando clang/libC++ e in -std = C++ 03 Questo stampa per me:

195.368µs 

Passaggio al - std = C++ 11 Ottengo:

16.422µs 

La vostra situazione potrebbe variare.

+2

+1: solo un'osservazione. Quando l'ho adattato per usare la mia classe 'Gizmo' qui sopra, i tempi delle versioni' move' e non -move' erano quasi identici. Questo era su MSVC10, usando 'boost :: chrono' piuttosto che' std :: chrono', versione x64 compilata. –

+0

@JohnDibling: interessante. Se capisci perché c'è una tale disparità nei nostri risultati, mi piacerebbe sentirne parlare. Una cosa da provare: metti una no-outs nel tuo costruttore di mosse. Non so se MSVC10 implementa questo o no. Sarei sorpreso se lo facesse considerando quanto in ritardo non fosse arrivato. E in realtà non mi aspetto che questo faccia la differenza per il membro vector :: erase. Ma comunque, è la prima cosa che proverei. Sono in esecuzione su un Intel Core i5 da 2,8 GHz (compilato per 64 bit). I tuoi risultati erano dell'ordine di poche centinaia di microsecondi o qualche decina di microsecondi? –