Dois-je stocker des objets entiers ou des pointeurs vers des objets dans des conteneurs?

162

Concevoir un nouveau système à partir de zéro. J'utiliserai la STL pour stocker des listes et des cartes de certains objets de longue durée.

Question: Dois-je m'assurer que mes objets ont des constructeurs de copie et stockent des copies d'objets dans mes conteneurs STL, ou est-il généralement préférable de gérer la vie et la portée moi-même et de simplement stocker les pointeurs vers ces objets dans mes conteneurs STL?

Je me rends compte que c'est un peu court sur les détails, mais je cherche la meilleure réponse «théorique» si elle existe, car je sais que ces deux solutions sont possibles.

Deux inconvénients très évidents à jouer avec les pointeurs: 1) Je dois gérer moi-même l'allocation / la désallocation de ces objets dans un périmètre au-delà de la STL. 2) Je ne peux pas créer un objet temporaire sur la pile et l'ajouter à mes conteneurs.

Y a-t-il autre chose qui me manque?

Stéphane
la source
36
Dieu j'aime ce site, c'est la question exacte à laquelle je pensais aujourd'hui ... merci d'avoir fait le travail de la poser pour moi :-)
eviljack
2
une autre chose intéressante est que nous devrions vérifier si le pointeur a été réellement ajouté à la collection et si ce n'est pas le cas, nous devrions probablement appeler delete pour éviter les fuites de mémoire ... if ((set.insert (pointer)). second = false) {delete pointer;}
javapowered

Réponses:

68

Étant donné que les gens s'interrogent sur l'efficacité de l'utilisation des pointeurs.

Si vous envisagez d'utiliser un std :: vector et si les mises à jour sont peu nombreuses et que vous parcourez souvent votre collection et que c'est un type non polymorphe, stocker des "copies" d'objets sera plus efficace car vous obtiendrez une meilleure localité de référence.

Otoh, si les mises à jour sont courantes, les pointeurs de stockage permettront d'économiser les coûts de copie / déplacement.

Torbjörn Gyllebring
la source
7
En termes de localisation du cache, le stockage des pointeurs dans le vecteur peut être efficace s'il est utilisé avec un allocateur personnalisé pour les pointes. L'allocateur personnalisé doit prendre soin de la localité du cache, par exemple en utilisant le placement new (voir en.wikipedia.org/wiki/Placement_syntax#Custom_allocators ).
amit le
47

Cela dépend vraiment de votre situation.

Si vos objets sont petits et que faire une copie de l'objet est léger, le stockage des données dans un conteneur stl est simple et plus facile à gérer à mon avis, car vous n'avez pas à vous soucier de la gestion de la durée de vie.

Si vos objets sont volumineux et que le fait d'avoir un constructeur par défaut n'a pas de sens, ou si les copies d'objets coûtent cher, le stockage avec des pointeurs est probablement la solution.

Si vous décidez d'utiliser des pointeurs vers des objets, jetez un œil à la bibliothèque de conteneurs Boost Pointer . Cette bibliothèque d'amplification enveloppe tous les conteneurs STL pour une utilisation avec des objets alloués dynamiquement.

Chaque conteneur de pointeur (par exemple ptr_vector) prend possession d'un objet lorsqu'il est ajouté au conteneur et gère la durée de vie de ces objets pour vous. Vous accédez également à tous les éléments d'un conteneur ptr_ par référence. Cela vous permet de faire des choses comme

class BigExpensive { ... }

// create a pointer vector
ptr_vector<BigExpensive> bigVector;
bigVector.push_back( new BigExpensive( "Lexus", 57700 ) );
bigVector.push_back( new BigExpensive( "House", 15000000 );

// get a reference to the first element
MyClass& expensiveItem = bigList[0];
expensiveItem.sell();

Ces classes encapsulent les conteneurs STL et fonctionnent avec tous les algorithmes STL, ce qui est vraiment pratique.

Il existe également des fonctionnalités permettant de transférer la propriété d'un pointeur dans le conteneur à l'appelant (via la fonction de libération dans la plupart des conteneurs).

Nick Haddad
la source
38

Si vous stockez des objets polymporhiques, vous devez toujours utiliser une collection de pointeurs de classe de base.

C'est-à-dire que si vous prévoyez de stocker différents types dérivés dans votre collection, vous devez stocker des pointeurs ou vous faire manger par le démon tranchant.

Torbjörn Gyllebring
la source
1
J'ai adoré le démon tranchant!
idichekop
22

Désolé de sauter dans 3 ans après l'événement, mais une mise en garde ici ...

Sur mon dernier gros projet, ma structure de données centrale était un ensemble d'objets assez simples. Environ un an après le début du projet, au fur et à mesure que les exigences évoluaient, j'ai réalisé que l'objet devait en fait être polymorphe. Il a fallu quelques semaines de chirurgie cérébrale difficile et désagréable pour corriger la structure des données en un ensemble de pointeurs de classe de base et pour gérer tous les dommages collatéraux lors du stockage d'objets, du casting, etc. Il m'a fallu quelques mois pour me convaincre que le nouveau code fonctionnait. Incidemment, cela m'a fait réfléchir sérieusement à la qualité du modèle objet de C ++.

Sur mon grand projet actuel, ma structure de données centrale est un ensemble d'objets assez simples. Environ un an après le début du projet (ce qui se trouve être aujourd'hui), j'ai réalisé que l'objet devait en fait être polymorphe. De retour sur le net, j'ai trouvé ce fil de discussion et trouvé le lien de Nick vers la bibliothèque de conteneurs de pointeurs Boost. C'est exactement ce que j'ai dû écrire la dernière fois pour tout réparer, alors je vais essayer cette fois-ci.

La morale, pour moi, en tout cas: si vos spécifications ne sont pas à 100% gravées dans le marbre, optez pour des pointeurs, et vous pourriez potentiellement vous épargner beaucoup de travail plus tard.

EML
la source
Les spécifications ne sont jamais gravées dans la pierre. Je ne pense pas que cela signifie que vous devriez utiliser exclusivement des conteneurs de pointeurs, bien que les conteneurs de pointeurs Boost semblent rendre cette option beaucoup plus attrayante. Je suis sceptique quant à la nécessité de réviser tout votre programme en une seule fois si vous décidez qu'un conteneur d'objets doit être converti en conteneur de pointeurs. Cela pourrait être le cas pour certains modèles. Dans ce cas, c'est un design fragile. Dans ce cas, ne blâmez pas votre problème sur la «faiblesse» des conteneurs d'objets.
allyourcode
Vous auriez pu laisser l'élément dans le vecteur avec une sémantique de valeur et faire le comportement polymorphe à l'intérieur.
Billy ONeal
19

Pourquoi ne pas tirer le meilleur parti des deux mondes: faites un conteneur de pointeurs intelligents (comme boost::shared_ptrou std::shared_ptr). Vous n'avez pas à gérer la mémoire et vous n'avez pas à gérer des opérations de copie volumineuses.

Branan
la source
En quoi cette approche est-elle différente de ce que Nick Haddad a suggéré en utilisant Boost Pointer Container Library?
Thorsten Schöning le
10
@ ThorstenSchöning std :: shared_ptr n'ajoute pas de dépendance sur boost.
James Johnston
Vous ne pouvez pas utiliser de pointeurs partagés pour gérer votre polymorphisme, vous risquez donc de manquer cette fonctionnalité avec cette approche, sauf si vous lancez explicitement des pointeurs
auserdude
11

En règle générale, le stockage des objets directement dans le conteneur STL est préférable car il est le plus simple, le plus efficace et le plus facile à utiliser.

Si votre objet lui-même a une syntaxe non copiable ou est un type de base abstrait, vous devrez stocker des pointeurs (le plus simple est d'utiliser shared_ptr)

Greg Rogers
la source
4
Ce n'est pas très efficace si vos objets sont volumineux et que vous déplacez fréquemment des éléments.
allyourcode
3

Vous semblez bien comprendre la différence. Si les objets sont petits et faciles à copier, stockez-les par tous les moyens.

Sinon, je penserais à stocker des pointeurs intelligents (pas auto_ptr, un pointeur intelligent de comptage de références) à ceux que vous allouez sur le tas. Évidemment, si vous optez pour des pointeurs intelligents, vous ne pouvez pas stocker les objets alloués à la pile temporaire (comme vous l'avez dit).

@ Torbjörn fait un bon point sur le tranchage.

Lou Franco
la source
1
Oh, et ne jamais jamais créer une collection de ce auto_ptr
Torbjörn Gyllebring
Oui, auto_ptr n'est pas un pointeur intelligent - il ne compte pas de références.
Lou Franco le
auto_ptr n'a pas non plus de sémantique de copie non destructive. L'acte d'attribution d'un auto_ptr de oneà anotherlibérera la référence de oneet changera one.
Andy Finkenstadt
2

Si les objets doivent être référencés ailleurs dans le code, stockez-les dans un vecteur de boost :: shared_ptr. Cela garantit que les pointeurs vers l'objet resteront valides si vous redimensionnez le vecteur.

C'est à dire:

std::vector<boost::shared_ptr<protocol> > protocols;
...
connection c(protocols[0].get()); // pointer to protocol stays valid even if resized

Si personne d'autre ne stocke les pointeurs vers les objets, ou si la liste ne s'agrandit pas et ne diminue pas, stockez simplement comme des objets anciens:

std::vector<protocol> protocols;
connection c(protocols[0]); // value-semantics, takes a copy of the protocol

la source
1

Cette question me dérange depuis un moment.

Je me penche sur le stockage des pointeurs, mais j'ai des exigences supplémentaires (enveloppes SWIG lua) qui pourraient ne pas s'appliquer à vous.

Le point le plus important de cet article est de le tester vous - même , en utilisant vos objets

Je l'ai fait aujourd'hui pour tester la vitesse d'appel d'une fonction membre sur une collection de 10 millions d'objets, 500 fois.

La fonction met à jour x et y en fonction de xdir et ydir (toutes les variables de membre flottant).

J'ai utilisé un std :: list pour contenir les deux types d'objets, et j'ai trouvé que stocker l'objet dans la liste est légèrement plus rapide que d'utiliser un pointeur. D'un autre côté, les performances étaient très proches, donc cela dépend de la façon dont elles seront utilisées dans votre application.

Pour référence, avec -O3 sur mon matériel, les pointeurs ont pris 41 secondes pour se terminer et les objets bruts ont pris 30 secondes pour se terminer.

Meleneth
la source