Std :: vector sta copiando gli oggetti con un push_back?


169

Dopo molte indagini con valgrind, ho concluso che std :: vector fa una copia di un oggetto che si desidera push_back.

È davvero vero? Un vettore non può conservare un riferimento o un puntatore di un oggetto senza una copia ?!

Grazie


20
Questo è un principio di base del C ++. Gli oggetti sono valori. L'assegnazione fa una copia. Due variabili che si riferiscono allo stesso oggetto non sono possibili a meno che non si modifichi il tipo con *o &per creare un puntatore o un riferimento.
Daniel Earwicker,

8
Il push_back di @DanielEarwicker effettivamente prende un riferimento. Solo dalla firma non è chiaro che ne farà una copia.
Brian Gordon,

3
@BrianGordon - Non dire che lo è! Da qui la necessità del principio guida. Anche così, possiamo dedurre qualcosa dalla firma di push_back: ci vuole un const&. O butta via il valore (inutile) o c'è un metodo di recupero. Quindi guardiamo la firma di back, e ritorna semplice &, quindi o il valore originale è stato copiato o constè stato silenziosamente gettato via (molto male: comportamento potenzialmente indefinito). Quindi supponendo che i designer di vectorfossero razionali ( vector<bool>non resistere), concludiamo che ne fanno delle copie.
Daniel Earwicker,

Risposte:


183

Sì, std::vector<T>::push_back()crea una copia dell'argomento e lo memorizza nel vettore. Se si desidera memorizzare i puntatori agli oggetti nel vettore, creare un std::vector<whatever*>invece di std::vector<whatever>.

Tuttavia, è necessario assicurarsi che gli oggetti a cui fanno riferimento i puntatori rimangano validi mentre il vettore ne contiene un riferimento (i puntatori intelligenti che utilizzano il linguaggio RAII risolvono il problema).


Vorrei anche notare che, se si utilizzano i puntatori non elaborati, ora si è responsabili della pulizia dopo di essi. Non c'è un buon motivo per farlo (non uno a cui riesco a pensare comunque), dovresti sempre usare un puntatore intelligente.
Ed S.

1
detto questo, non dovresti usare std :: auto_ptr nei contenitori stl, per maggiori informazioni: why-is-it-wrong-to-use-stdauto-ptr-with-standard-containers
OriginalCliche

24
Dal momento che C ++ 11, push_backeseguirà una mossa anziché una copia se l'argomento è un riferimento di valore. (Gli oggetti possono essere convertiti in riferimenti di valore con std::move().)
emlai

2
@tuple_cat il tuo commento dovrebbe dire "se l'argomento è un valore". (Se l'argomento è il nome di un'entità dichiarata come riferimento al valore, allora l'argomento è in realtà un valore e non verrà spostato da) - controlla la mia modifica alla risposta di "Karl Nicoll" che ha inizialmente commesso quell'errore
MM

Di seguito c'è una risposta, ma per chiarire: dal momento che C ++ 11 usa anche emplace_backper evitare qualsiasi copia o spostamento (costruisci un oggetto sul posto fornito dal contenitore).
Wormer,

34

Sì, std::vectorarchivia le copie. Come dovrebbe vectorsapere quali sono i tempi di vita previsti dei tuoi oggetti?

Se si desidera trasferire o condividere la proprietà degli oggetti utilizzare i puntatori, possibilmente i puntatori intelligenti come shared_ptr(presenti in Boost o TR1 ) per facilitare la gestione delle risorse.


3
impara a usare shared_ptr: fanno esattamente quello che vuoi. Il mio linguaggio preferito è typedef boost :: shared_ptr <Foo> FooPtr; Quindi crea i contenitori di FooPtrs
pm100,

3
@ pm100 - Sai boost::ptr_vector?
Manuel,

2
Mi piace anche usare class Foo { typedef boost::shared_ptr<Foo> ptr; };per scrivere Foo::ptr.
Rupert Jones,

2
@ pm100 - shared_ptrnon è esattamente fuoco e dimentica. Vedere stackoverflow.com/questions/327573 e stackoverflow.com/questions/701456
Daniel Earwicker

2
shared_ptr è utile se si possiede una proprietà condivisa, ma è generalmente abusato. unique_ptr o boost scoped_ptr hanno molto più senso quando la proprietà è chiara.
Nemanja Trifunovic,

28

Da C ++ 11 in poi, tutti i contenitori standard ( std::vector, std::map, ecc) semantica supporto mossa, il che significa che ora è possibile passare rvalues di contenitori standard e di evitare una copia:

// Example object class.
class object
{
private:
    int             m_val1;
    std::string     m_val2;

public:
    // Constructor for object class.
    object(int val1, std::string &&val2) :
        m_val1(val1),
        m_val2(std::move(val2))
    {

    }
};

std::vector<object> myList;

// #1 Copy into the vector.
object foo1(1, "foo");
myList.push_back(foo1);

// #2 Move into the vector (no copy).
object foo2(1024, "bar");
myList.push_back(std::move(foo2));

// #3 Move temporary into vector (no copy).
myList.push_back(object(453, "baz"));

// #4 Create instance of object directly inside the vector (no copy, no move).
myList.emplace_back(453, "qux");

In alternativa puoi utilizzare vari puntatori intelligenti per ottenere principalmente lo stesso effetto:

std::unique_ptr esempio

std::vector<std::unique_ptr<object>> myPtrList;

// #5a unique_ptr can only ever be moved.
auto pFoo = std::make_unique<object>(1, "foo");
myPtrList.push_back(std::move(pFoo));

// #5b unique_ptr can only ever be moved.
myPtrList.push_back(std::make_unique<object>(1, "foo"));

std::shared_ptr esempio

std::vector<std::shared_ptr<object>> objectPtrList2;

// #6 shared_ptr can be used to retain a copy of the pointer and update both the vector
// value and the local copy simultaneously.
auto pFooShared = std::make_shared<object>(1, "foo");
objectPtrList2.push_back(pFooShared);
// Pointer to object stored in the vector, but pFooShared is still valid.

2
Nota che std::make_uniqueè (fastidiosamente) disponibile solo in C ++ 14 e versioni successive. Assicurati di dire al tuo compilatore di impostare la sua conformità standard di conseguenza se vuoi compilare questi esempi.
Laryx Decidua,

In 5a puoi usare auto pFoo =per evitare la ripetizione; e tutti i std::stringcast possono essere rimossi (c'è una conversione implicita da letterali stringa a std::string)
MM

2
@ user465139 make_uniquepuò essere facilmente implementato in C ++ 11, quindi è solo una leggera seccatura per qualcuno bloccato con un compilatore C ++ 11
MM

1
@MM: davvero. Ecco l'implementazione del libro di testo:template<typename T, typename... Args> unique_ptr<T> make_unique(Args&&... args) { return unique_ptr<T>{new T{args...}}; }
Laryx Decidua,

1
@Anakin - Sì, dovrebbero farlo, ma solo se copi. Se lo usi std::move()con std::shared_ptr, il puntatore condiviso originale potrebbe avere il puntatore cambiato poiché la proprietà è stata passata al vettore. Vedi qui: coliru.stacked-crooked.com/a/99d4f04f05e5c7f3
Karl Nicoll

15

std :: vector crea sempre una copia di tutto ciò che viene archiviato nel vettore.

Se si mantiene un vettore di puntatori, verrà creata una copia del puntatore, ma non l'istanza a cui punta il puntatore. Se hai a che fare con oggetti di grandi dimensioni, puoi (e probabilmente dovresti) usare sempre un vettore di puntatori. Spesso, utilizzare un vettore di puntatori intelligenti di un tipo appropriato è utile per motivi di sicurezza, poiché altrimenti la gestione della durata degli oggetti e la gestione della memoria possono essere complicate.


3
non dipende dal tipo. Fa sempre una copia. Se si tratta di un puntatore, viene
creata

Hai ragione entrambi. Tecnicamente sì, ne fa sempre una copia. In pratica, se gli passi un puntatore sull'oggetto, copia il puntatore, non l'oggetto. In modo sicuro, è necessario utilizzare un puntatore intelligente appropriato.
Steven Sudit,

1
Sì, sta sempre copiando - Tuttavia, "l'oggetto" a cui si riferisce l'OP è molto probabilmente una classe o una struttura, quindi mi riferivo al fatto che stia copiando "Oggetto" dipende dalla definizione. Per quanto mal formulata.
Reed Copsey,

3

Non solo std :: vector crea una copia di ciò che stai respingendo, ma la definizione della raccolta afferma che lo farà e che non puoi usare oggetti senza la semantica della copia corretta all'interno di un vettore. Quindi, ad esempio, non usi auto_ptr in un vettore.


2

Rilevante in C ++ 11 è la emplacefamiglia di funzioni membro, che consente di trasferire la proprietà degli oggetti spostandoli in contenitori.

Il linguaggio dell'uso sarebbe simile

std::vector<Object> objs;

Object l_value_obj { /* initialize */ };
// use object here...

objs.emplace_back(std::move(l_value_obj));

Lo spostamento per l'oggetto lvalue è importante in quanto altrimenti verrebbe inoltrato come riferimento o riferimento const e il costruttore dello spostamento non verrebbe chiamato.


0

se non vuoi le copie; quindi il modo migliore è usare un vettore puntatore (o un'altra struttura che serve per lo stesso obiettivo). se vuoi le copie; usa direttamente push_back (). non hai altra scelta.


1
Una nota sui vettori di puntatori: il vettore <shared_ptr <obj>> è molto più sicuro del vettore <obj *> e shared_ptr fa parte dello standard dell'anno scorso.
rich.e

-1

Perché ci sono volute molte indagini su Valgrind per scoprirlo! Provalo solo con un semplice codice, ad es

std::vector<std::string> vec;

{
      std::string obj("hello world");
      vec.push_pack(obj);
}

std::cout << vec[0] << std::endl;  

Se viene stampato "ciao mondo", l'oggetto deve essere stato copiato


4
Questo non costituisce una prova. Se l'oggetto non fosse copiato, la tua ultima affermazione sarebbe un comportamento indefinito e potrebbe stampare ciao.
Mat

4
il test corretto sarebbe modificare uno dei due dopo l'inserimento. Se fossero lo stesso oggetto (se il vettore memorizzava un riferimento), entrambi sarebbero stati modificati.
Francesco Dondi,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.