66 votes

C++ Vecteur de Pointeurs sur des Objets

Je suis à l'aide d'un vecteur de pointeurs sur des objets. Ces objets sont issus d'une classe de base, et sont alloués dynamiquement et stockées.

Par exemple, j'ai quelque chose comme:

vector<Enemy*> Enemies;

et je vais être découlant de l'Ennemi de classe, puis allouer dynamiquement de la mémoire pour la classe dérivée, comme ceci:

enemies.push_back(new Monster());

Ce sont des choses que j'ai besoin d'être conscient de la éviter les fuites de mémoire et d'autres problèmes?

145voto

GManNickG Points 155079

std::vector sera de gérer la mémoire pour vous, comme toujours, mais cette de la mémoire de pointeurs, pas des objets.

Ce que cela signifie, c'est que vos classes seront perdues dans la mémoire une fois que votre vecteur est hors de portée. Par exemple:

#include <vector>

struct base
{
    virtual ~base() {}
};

struct derived : base {};

typedef std::vector<base*> container;

void foo()
{
    container c;

    for (unsigned i = 0; i < 100; ++i)
        c.push_back(new derived());

} // leaks here! frees the pointers, doesn't delete them (nor should it)

int main()
{
    foo();
}

Ce que vous devez faire est de vous assurer que vous supprimez tous les objets avant que le vecteur est hors de portée:

#include <algorithm>
#include <vector>

struct base
{
    virtual ~base() {}
};

struct derived : base {};

typedef std::vector<base*> container;

template <typename T>
void delete_pointed_to(T* const ptr)
{
    delete ptr;
}

void foo()
{
    container c;

    for (unsigned i = 0; i < 100; ++i)
        c.push_back(new derived());

    // free memory
    std::for_each(c.begin(), c.end(), delete_pointed_to<base>);
}

int main()
{
    foo();
}

C'est difficile à maintenir, si, parce que nous ne devons pas oublier d'effectuer une action. Plus important encore, si une exception se fait entre la répartition des éléments et la désallocation de la boucle, la désallocation de la boucle ne jamais exécuter et vous êtes coincé avec la fuite de mémoire, de toute façon! Cela s'appelle de l'exception de sécurité et c'est une critique de la raison pourquoi de libération de la mémoire qui doit être fait automatiquement.

Mieux serait si les pointeurs supprimé eux-mêmes. Les thèses sont appelés des pointeurs intelligents, et de la bibliothèque standard fournit std::unique_ptr et std::shared_ptr.

std::unique_ptr représente un cas unique (non partagé, un seul propriétaire) pointeur vers une certaine ressource. Ce devrait être par défaut de votre pointeur intelligent, et dans l'ensemble le remplacement complet de tout pointeur brut utiliser.

auto myresource = /*std::*/make_unique<derived>(); // won't leak, frees itself

std::make_unique est manquant dans le C++11 est la norme de contrôle, mais vous pouvez faire un vous-même. Pour créer directement un unique_ptr (pas recommandé en make_unique si vous le pouvez), faites ceci:

std::unique_ptr<derived> myresource(new derived());

Unique pointeurs ont la sémantique de déplacement; ils ne peuvent pas être copiés:

auto x = myresource; // error, cannot copy
auto y = std::move(myresource); // okay, now myresource is empty

Et c'est tout que nous avons besoin de l'utiliser dans un conteneur:

#include <memory>
#include <vector>

struct base
{
    virtual ~base() {}
};

struct derived : base {};

typedef std::vector<std::unique_ptr<base>> container;

void foo()
{
    container c;

    for (unsigned i = 0; i < 100; ++i)
        c.push_back(make_unique<derived>());

} // all automatically freed here

int main()
{
    foo();
}

shared_ptr a de comptage de références sémantique de copie; il permet de multiples propriétaires de partage de l'objet. Il permet de suivre combien de shared_ptrs existe pour un objet, et lorsque le dernier cesse d'exister (le comptage à zéro), il libère le pointeur. La copie augmente simplement le nombre de références (et en déplaçant les transferts de propriété à un plus faible, presque sans coût). Vous rendre avec des std::make_shared (ou directement comme indiqué ci-dessus, mais parce qu' shared_ptr a interne à rendre les allocations, il est généralement plus efficace et techniquement plus exception-sûr à utiliser, make_shared).

#include <memory>
#include <vector>

struct base
{
    virtual ~base() {}
};

struct derived : base {};

typedef std::vector<std::shared_ptr<base>> container;

void foo()
{
    container c;

    for (unsigned i = 0; i < 100; ++i)
        c.push_back(std::make_shared<derived>());

} // all automatically freed here

int main()
{
    foo();
}

Rappelez-vous, en général, vous souhaitez utiliser std::unique_ptr comme un défaut, car il est plus léger. En outre, std::shared_ptr peut être construit à partir d'un std::unique_ptr (mais pas vice-versa), il est donc bon de commencer petit.

Sinon, vous pouvez utiliser un conteneur créé pour stocker des pointeurs vers des objets, comme un boost::ptr_container:

#include <boost/ptr_container/ptr_vector.hpp>

struct base
{
    virtual ~base() {}
};

struct derived : base {};

// hold pointers, specially
typedef boost::ptr_vector<base> container;

void foo()
{
    container c;

    for (int i = 0; i < 100; ++i)
        c.push_back(new Derived());

} // all automatically freed here

int main()
{
    foo();
}

Alors qu' boost::ptr_vector<T> avaient évident utiliser en C++03, je ne peux pas parler de la pertinence maintenant, parce que nous pouvons utiliser std::vector<std::unique<T>> avec probablement peu ou pas comparables, les frais généraux, mais cette affirmation doit être testé.

Peu importe, jamais explicitement gratuit choses dans votre code. Envelopper les choses pour s'assurer de la gestion des ressources est traitée automatiquement. Vous ne devriez pas avoir cru de posséder des pointeurs dans votre code.

Comme un défaut dans un jeu, je serais probablement aller avec std::vector<std::shared_ptr<T>>. Nous nous attendons à ce partage de toute façon, il est assez rapide jusqu'à ce que le profilage dit le contraire, c'est sûr, et il est facile à utiliser.

9voto

Naveen Points 37095

Je pars du principe suivant:

  1. Vous avez un vecteur comme vecteur< base* >
  2. Vous poussez les pointeurs pour ce vecteur après l'attribution des objets sur le tas
  3. Vous voulez faire un push_back de dérivés* pointeur dans ce vecteur.

Suivant les choses viennent à mon esprit:

  1. Vecteur sera pas libérer la mémoire de l'objet pointé par le pointeur. Vous devez le supprimer de lui-même.
  2. Rien de spécifique à l'vecteur, mais la classe de base destructeur doit être virtuel.
  3. vector< base* > et vector< dérivés* > sont deux totalement différents types.

9voto

sbi Points 100828

Le problème avec l'aide d' vector<T*> , c'est que, chaque fois que le vecteur est hors de portée de façon inattendue (comme lorsqu'une exception est levée), le vecteur nettoie après vous-même, mais ce ne sera libéré que le mémoire qu'il gère pour tenir le pointeur, pas la mémoire allouée pour ce que les pointeurs sont référence. Donc GMan de l' delete_pointed_to fonction est d'une valeur limitée, car il ne fonctionne que quand rien ne va mal.

Ce que vous devez faire est d'utiliser un pointeur intelligent:

vector< std::tr1::shared_ptr<Enemy> > Enemies;

(Si votre std lib vient sans TR1, utilisez boost::shared_ptrà la place.) Sauf pour de très rares cas de coin (les références circulaires) cela supprime simplement le problème de la durée de vie des objets.

Edit: Note que GMan, dans sa réponse détaillée, mentionne cela, aussi.

-1voto

dennis bednar Points 1

Une chose à être très prudent SI il y a deux Monstre() des objets DÉRIVÉS dont les contenus sont identiques en valeur. Supposons que vous souhaitiez supprimer les DOUBLONS du Monstre objets à partir de votre vecteur (de la classe de BASE des pointeurs vers des DÉRIVÉS Monstre objets). Si vous avez utilisé la norme idiome pour la suppression des doublons (tri, unique, effacer: voir le LIEN #2], vous serez confronté à des problèmes de fuite de mémoire, et/ou en double supprimer les problèmes, conduisant éventuellement à la SEGMENTATION VOIOLATIONS (j'ai personnellement vu ces problèmes sur la machine LINUX).

Le problème avec les std::unique() est que les doublons dans la [duplicatePosition,fin) de la gamme [inclusive, exclusive) à la fin du vecteur ne sont pas définis comme ?. Ce qui peut arriver, c'est que indéfini ((?) les éléments peuvent être extra double ou d'un manque en double.

Le problème est que std::unique() n'est pas adapté pour gérer un vecteur de pointeurs correctement. La raison en est que std::copies uniques uniques à partir de la fin du vecteur "bas" vers le début du vecteur. Pour un vecteur d'objets simples ce invoque la COPIE CTOR, et si la COPIE CTOR est écrit correctement, il n'y a pas de problème de fuites de mémoire. Mais quand sa un vecteur de pointeurs, il n'existe aucune COPIE CTOR autre que "copie bit à bit", et donc le pointeur lui-même est tout simplement copié.

Il y a des façons de résoudre ces fuite de mémoire autre que l'aide d'un pointeur intelligent. Une manière d'écrire votre propre version légèrement modifiée de std::unique() comme "your_company::unique()". Le truc de base, c'est qu'au lieu de la copie d'un élément, vous souhaitez échanger deux éléments. Et vous devez être sûr que le au lieu de comparer deux pointeurs, vous appelez BinaryPredicate qui suit les deux pointeurs vers les objets eux-mêmes, et de comparer le contenu de ces deux "Monstres" des objets dérivés.

1) @SEE_ALSO: http://www.cplusplus.com/reference/algorithm/unique/

2) @SEE_ALSO: le moyen le Plus efficace pour effacer les doublons et de tri en c++ vecteur?

2ème lien est très bien écrit, et va travailler pour un std::vector, mais a des fuites de mémoire, libère double (parfois lieu à des violations de SEGMENTATION) pour un std::vector

3) @SEE_ALSO: valgrind(1). Cette "fuite de mémoire" de l'outil sur LINUX qui est étonnant dans ce qu'il peut trouver! Je vous recommande FORTEMENT de l'utiliser!

J'espère poster une belle version de "my_company::unique()" dans un futur post. Maintenant, ce n'est pas parfait, parce que je veux la 3-arg version BinaryPredicate de travailler de façon transparente, soit un pointeur de fonction ou d'un FONCTEUR, et je vais avoir quelques problèmes de maniabilité à la fois correctement. SI je ne peux pas résoudre ces problèmes, je vais poster ce que j'ai, et de laisser la communauté ont un aller à une amélioration sur ce que j'ai fait jusqu'à présent.

Merci .. je trouve ce site TRÈS UTILE.

PS: restez à l'écoute !!

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X