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.