Est-ce que std :: vector copie les objets avec un push_back?


169

Après de nombreuses recherches avec valgrind, j'ai conclu que std :: vector fait une copie d'un objet que vous voulez push_back.

Est-ce vraiment vrai? Un vecteur ne peut pas garder une référence ou un pointeur d'un objet sans copie?!

Merci


20
C'est un principe de base du C ++. Les objets sont des valeurs. L'affectation fait une copie. Deux variables faisant référence au même objet ne sont pas possibles sauf si vous modifiez le type avec *ou &pour créer un pointeur ou une référence.
Daniel Earwicker

8
@DanielEarwicker push_back prend en fait une référence. Il n'est pas clair d'après la signature seule qu'il en fera une copie.
Brian Gordon

3
@BrianGordon - Ne le dis pas! D'où la nécessité du principe directeur. Même ainsi, on peut déduire quelque chose de la signature de push_back: il faut un const&. Soit il jette la valeur (inutile), soit il existe une méthode de récupération. Nous regardons donc la signature de back, et elle retourne brut &, donc soit la valeur d'origine a été copiée, soit la consta été rejetée silencieusement (très mauvais: comportement potentiellement indéfini). Donc, en supposant que les concepteurs de vectorétaient rationnels ( vector<bool>nonobstant), nous concluons qu'il fait des copies.
Daniel Earwicker

Réponses:


183

Oui, std::vector<T>::push_back()crée une copie de l'argument et le stocke dans le vecteur. Si vous souhaitez stocker des pointeurs vers des objets dans votre vecteur, créez un à la std::vector<whatever*>place de std::vector<whatever>.

Cependant, vous devez vous assurer que les objets référencés par les pointeurs restent valides tant que le vecteur contient une référence à eux (les pointeurs intelligents utilisant l'idiome RAII résolvent le problème).


Je voudrais également noter que, si vous utilisez des pointeurs bruts, vous êtes maintenant responsable du nettoyage après eux. Il n'y a aucune bonne raison de le faire (pas une à laquelle je pense de toute façon), vous devriez toujours utiliser un pointeur intelligent.
Ed S.

1
cela dit, vous ne devriez pas utiliser std :: auto_ptr dans les conteneurs stl, pour plus d'informations: pourquoi-est-il-mal-utiliser-stdauto-ptr-avec-conteneurs-standard
OriginalCliche

24
Depuis C ++ 11, push_backeffectuera un déplacement au lieu d'une copie si l'argument est une référence rvalue. (Les objets peuvent être convertis en références rvalue avec std::move().)
emlai

2
@tuple_cat votre commentaire devrait dire "si l'argument est une rvalue". (Si l'argument est le nom d'une entité déclarée comme référence rvalue, alors l'argument est en fait une lvalue et ne sera pas déplacé de) - vérifiez ma modification à la réponse de "Karl Nicoll" qui a fait cette erreur au départ
MM

Il y a une réponse ci-dessous mais pour être clair: puisque C ++ 11 utilise également emplace_backpour éviter toute copie ou tout déplacement (objet de construction en place fourni par le conteneur).
Wormer

34

Oui, std::vectorstocke des copies. Comment vectorsavoir quelles sont les durées de vie attendues de vos objets?

Si vous souhaitez transférer ou partager la propriété des objets, utilisez des pointeurs, éventuellement des pointeurs intelligents comme shared_ptr(trouvés dans Boost ou TR1 ) pour faciliter la gestion des ressources.


3
apprenez à utiliser shared_ptr - ils font exactement ce que vous voulez. Mon idiome préféré est typedef boost :: shared_ptr <Foo> FooPtr; Puis faites des conteneurs de FooPtrs
pm100

3
@ pm100 - Le savez-vous boost::ptr_vector?
Manuel

2
J'aime aussi utiliser class Foo { typedef boost::shared_ptr<Foo> ptr; };pour simplement écrire Foo::ptr.
Rupert Jones

2
@ pm100 - shared_ptrn'est pas exactement le feu et oublier. Voir stackoverflow.com/questions/327573 et stackoverflow.com/questions/701456
Daniel Earwicker

2
shared_ptr est bon si vous avez une propriété partagée, mais il est généralement surutilisé. unique_ptr ou boost scoped_ptr ont beaucoup plus de sens lorsque la propriété est claire.
Nemanja Trifunovic

28

A partir de C ++ 11 partir, tous les conteneurs standard ( std::vector, std::map, etc.) , supportent la sémantique de mouvement, ce qui signifie que vous pouvez maintenant passer rvalues aux conteneurs standards et éviter une copie:

// 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");

Vous pouvez également utiliser divers pointeurs intelligents pour obtenir le même effet:

std::unique_ptr exemple

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 exemple

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
Notez que std::make_uniquec'est (ennuyeusement) disponible uniquement en C ++ 14 et supérieur. Assurez-vous de dire à votre compilateur de définir sa conformité standard en conséquence si vous souhaitez compiler ces exemples.
Laryx Decidua

En 5a, vous pouvez utiliser auto pFoo =pour éviter les répétitions; et toutes les std::stringtranstypages peuvent être supprimés (il y a une conversion implicite des littéraux de chaîne en std::string)
MM

2
@ user465139 make_uniquepeut facilement être implémenté en C ++ 11, donc ce n'est qu'un léger ennui pour quelqu'un coincé avec un compilateur C ++ 11
MM

1
@MM: En effet. Voici la mise en œuvre du manuel:template<typename T, typename... Args> unique_ptr<T> make_unique(Args&&... args) { return unique_ptr<T>{new T{args...}}; }
Laryx Decidua

1
@Anakin - Oui, ils devraient le faire, mais seulement si vous copiez. Si vous utilisez std::move()avec std::shared_ptr, le pointeur partagé d'origine peut avoir son pointeur modifié car la propriété a été transmise au vecteur. Voir ici: coliru.stacked-crooked.com/a/99d4f04f05e5c7f3
Karl Nicoll

15

std :: vector fait toujours une copie de tout ce qui est stocké dans le vecteur.

Si vous conservez un vecteur de pointeurs, alors il fera une copie du pointeur, mais pas l'instance vers laquelle le pointeur pointe. Si vous avez affaire à de gros objets, vous pouvez (et devriez probablement) toujours utiliser un vecteur de pointeurs. Souvent, l'utilisation d'un vecteur de pointeurs intelligents d'un type approprié est bonne pour des raisons de sécurité, car la gestion de la durée de vie des objets et de la gestion de la mémoire peut être délicate dans le cas contraire.


3
cela ne dépend pas du type. Il fait toujours une copie. Si c'est un pointeur fait une copie du pointeur
pm100

Vous avez tous les deux raison. Techniquement, oui, il fait toujours une copie. En pratique, si vous lui passez un pointeur vers l'objet, il copie le pointeur, pas l'objet. En toute sécurité, vous devez utiliser un pointeur intelligent approprié.
Steven Sudit le

1
Oui, il est toujours en train de copier - Cependant, l '"objet" auquel l'OP fait référence est très probablement une classe ou une structure, donc je faisais référence au fait que la copie de l' "objet" dépend de la définition. Mal formulé, cependant.
Reed Copsey

3

Non seulement std :: vector fait une copie de tout ce que vous repoussez, mais la définition de la collection déclare qu'elle le fera et que vous ne pouvez pas utiliser d'objets sans la sémantique de copie correcte dans un vecteur. Ainsi, par exemple, vous n'utilisez pas auto_ptr dans un vecteur.


2

La emplacefamille de fonctions membres, qui vous permet de transférer la propriété des objets en les déplaçant dans des conteneurs, est pertinente dans C ++ 11 .

L'idiome d'utilisation ressemblerait à

std::vector<Object> objs;

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

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

Le déplacement de l'objet lvalue est important car sinon il serait transféré en tant que référence ou référence const et le constructeur de déplacement ne serait pas appelé.


0

si vous ne voulez pas les copies; alors la meilleure façon est d'utiliser un vecteur de pointeur (ou une autre structure qui sert au même but). si vous voulez les copies; utilisez directement push_back (). vous n'avez pas d'autre choix.


1
Une note à propos des vecteurs de pointeur: vector <shared_ptr <obj>> est beaucoup plus sûr que vector <obj *> et shared_ptr fait partie de la norme depuis l'année dernière.
rich.e

-1

Pourquoi a-t-il fallu beaucoup d'investigations valgrind pour le découvrir! Prouvez-le vous-même avec un code simple, par exemple

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

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

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

Si "hello world" est imprimé, l'objet doit avoir été copié


4
Cela ne constitue pas une preuve. Si l'objet n'était pas copié, votre dernière déclaration serait un comportement indéfini et pourrait afficher bonjour.
Mat

4
le test correct serait de modifier l'un des deux après l'insertion. S'il s'agissait du même objet (si le vecteur stockait une référence), les deux seraient modifiés.
Francesco Dondi
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.