2010-04-05 10 views
22

Ho un programma C++ che utilizza un elenco std :: contenente istanze di una classe. Se chiamo ad es. myList.push_back(MyClass(variable)); attraversa il processo di creazione di una variabile temporanea, quindi la copia immediatamente nel vettore e successivamente elimina la variabile temporanea. Non è così efficiente come voglio e fa schifo quando hai bisogno di una copia profonda.Come inizializzare un vettore/elenco STL con una classe senza invocare il costruttore di copie

Mi piacerebbe avere il costruttore della mia classe new qualcosa e non dover implementare un costruttore di copie solo per allocare la memoria per la seconda volta e sprecare tempo di esecuzione. Preferisco anche non dover trovare immediatamente l'istanza di classe dal vettore/elenco e quindi allocare manualmente la memoria (o fare qualcosa di orribile come allocare la memoria nel costruttore di copie stesso).

C'è un modo per aggirare questo (non sto utilizzando Visual Studio BTW)?

+1

Che compilatore stai utilizzando? Credo che questo caso sia generalmente ottimizzato nei nuovi compilatori. –

+0

Stai eseguendo questa analisi con una build ottimizzata? –

+0

È necessario testare con una build di rilascio. Potrebbe farlo in modalità DEBUG, ma utilizzare RVO (ottimizzazione del valore di ritorno) in modalità di rilascio, che elimina la copia. – Nate

risposta

9

C++ 0x i costruttori di mosse sono una soluzione parziale: anziché il costruttore di copia invocato, il costruttore di movimento sarebbe. Il costruttore di movimento è come il costruttore di copie eccetto che è autorizzato a invalidare l'argomento sorgente.

C++ 0x aggiunge un'altra funzione che farebbe esattamente ciò che si desidera: emplace_back. (N3092 §23.2.3) Passa gli argomenti al costruttore, quindi chiama il costruttore con tali argomenti (per ... e inoltro) in modo che nessun altro costruttore possa mai essere invocato.

Per quanto riguarda C++ 03, l'unica opzione è aggiungere uno stato non inizializzato alla classe. Eseguire la costruzione effettiva in un'altra funzione chiamata immediatamente dopo push_back. boost::optional potrebbe aiutarti a evitare l'inizializzazione dei membri della classe, ma a sua volta richiede loro essere copia-costruibile. Oppure, come dice Fred, realizzare la stessa cosa con puntatori intelligenti inizialmente vuoti.

+0

Questo è più quello che sto cercando. Ho un compilatore abilitato C++ 0x, e come la convenienza di std :: liste senza l'inconveniente di allocazione memoria/heap di un gran numero di puntatori. Divertente Non ho mai sentito parlare di emplace_back, che è quello che ottengo solo guardando la voce wikipedia per C++ 0x. Dove posso trovare ulteriori informazioni a riguardo? – Warpspace

+0

@Warpspace: scaricare N3092 (google per "C++ N3092") e vedere Domande frequenti su C++ 0x di Stroustrup http://www2.research.att.com/~bs/C++0xFAQ.html. Ho notato che Microsoft ha una documentazione errata per 'emplace', sostenendo che usa la semantica del movimento. (La stessa cosa si ottiene con 'push_back (move (...))'.) Quindi se sei su MSVC potresti essere ancora sfortunato per il momento. – Potatoswatter

4

In effetti, il compilatore potrebbe elidere la copia in questo caso.

Se il compilatore non esegue questa operazione, un modo per evitare la copia consiste nel fare in modo che l'elenco contenga puntatori anziché istanze. Potresti usare puntatori intelligenti per ripulire gli oggetti per te.

+2

Il compilatore non può elidere la copia. L'oggetto è costruito sullo stack e passato con riferimento const a 'vector <> :: allocator_type :: construct' per spostarlo nell'heap. È costruito prima che la chiamata a 'push_back' abbia inizio e il percorso di destinazione non sia noto fino a quando non viene visualizzato' allocator_type :: allocate' all'interno di 'push_back'. Le forze del tempo e dello spazio non cooperano. – Potatoswatter

2

Controllare la libreria ptr_container di Boost. Uso il ptr_vector in particolare:

boost::ptr_vector<Foo> c; 
c.push_back(new Foo(1,2,3)); 
c[0].doSomething() 

e quando esce dall'ambito, delete saranno chiamati ciascun elemento del vettore.

+1

... senza l'overhead di 'shared_ptr' - +1 – vladr

1

Utilizzare un shared_ptr o shared_array per gestire la memoria che la classe desidera allocare. Quindi il copy-constructor fornito dal compilatore semplicemente incrementerà un conteggio di riferimento mentre le copie dello shared_ptr vengono copiate. È un concetto di utilizzo importante per i contenitori standard che i tuoi elementi sono economici da copiare. La libreria standard esegue copie dappertutto.

5

I costruttori di movimento C++ 0x (disponibili con VC++ 2010 e recenti compilatori GNU) sono esattamente ciò che state cercando.

+0

I costruttori di spostamento richiedono uno stato non inizializzato per l'oggetto. Se ha uno stato non inizializzato, può costruirlo di default con 'push_back (MyClass())' e quindi "inizializza" il suo oggetto sul posto. Quindi non penso che "move" risolva qualcosa qui. – Potatoswatter

+0

Non sono sicuro che sia il mio compilatore o no, ma con C++ 0x (sotto G ++) abilitato, il costruttore di movimento sembra non essere chiamato, dato che il mio oggetto viene cancellato due volte (implicando che il costruttore di copia è usato). Quello che Potatocorn menziona è la mia attuale implementazione, che sto cercando di evitare a causa della severa duplicazione del codice (o della necessità di una funzione dedicata). – Warpspace

9

Ahem. Nell'interesse della scienza, ho montata su un piccolo programma di test per verificare se il compilatore elide la copia o no:

#include <iostream> 
#include <list> 
using namespace std; 

class Test 
{ 
public: 
    Test() { cout<<"Construct\n"; } 
    Test(const Test& other) { cout<<"Copy\n"; } 
    Test& operator=(const Test& other) { cout<<"Assign\n"; return (*this); } 
}; 

Test rvo() { return Test(); } 
int main() 
{ 
    cout<<"Testing rvo:\n"; 
    Test t = rvo(); 
    cout<<"Testing list insert:\n"; 
    list<Test> l; 
    l.push_back(Test()); 
} 

Ed ecco la mia uscita su MSVC++ 2008:

 
Testing rvo: 
Construct 
Testing list insert: 
Construct 
Copy 

È uguale per entrambe le versioni di debug e release: RVO funziona, il passaggio temporaneo di oggetti non è ottimizzato.
Se non sbaglio, lo Rvalue references aggiunto nello standard C++ 0x ha lo scopo di risolvere questo problema.

+1

+1 per ** science **! –

0

Suggerirei di utilizzare uno std::vector<std::unique_ptr>, perché rilascia automaticamente la memoria quando necessario, con un sovraccarico minore di std::shared_ptr. L'unica avvertenza è che questo puntatore non ha il conteggio dei riferimenti, quindi bisogna stare attenti a non copiare il puntatore da qualche altra parte, per evitare che i dati vengano cancellati quando vengono ancora utilizzati altrove.

Problemi correlati