2010-07-16 6 views
0

dire donc j'ai une classe comme ceci:Membres des données C++ Pointer: Qui devrait les supprimer?

class A { 
    public: 
     A(SomeHugeClass* huge_object) 
      : m_huge_object(huge_object) {} 
    private: 
     SomeHugeClass* m_huge_object;   
}; 

Si quelqu'un utilise le constructeur comme ceci:

A* foo = new A(new SomeHugeClass()); 

la responsabilité de qui est-il d'appeler supprimer sur l'objet newed dans le constructeur? Dans ce cas, la portée dans laquelle le constructeur A a été appelé ne peut supprimer que foo puisque SomeHugeClass est anonyme.

Cependant, que se passe-t-il si quelqu'un utilise le constructeur comme ça?

SomeHugeClass* hugeObj = new SomeHugeClass(); 
A* foo = new A(hugeObj); 

Ensuite, l'appelant peut appeler supprimer énormeObj à un moment donné, non?

Est-ce que cette implémentation de A fuite mémoire sur la destruction? Je travaille sur un projet avec beaucoup de composition d'objet fait de cette façon et autant que j'aimerais utiliser des pointeurs intelligents, je dois parler au projet conduit à changer de code ancien pour en profiter avant Je peux.

+0

Vous devez 'hugeObj' dans l'initialisation de votre dernier extrait de code; Dans le cas contraire, vous risquez de rencontrer un comportement indéfini lorsque vous utiliserez 'foo'. – Gorpik

+0

^Bonne prise. Modifier cela. – RyanG

+1

Personne ne peut dire qui devrait supprimer les membres du pointeur dans votre cas: nous manquons de contexte pour répondre de manière fiable. Je pense que cela dépend de ce que fait votre classe et de ce qu'elle représente. Il peut s'agir d'une classe qui fait référence à des données externes et dont la documentation spécifie que sa durée de vie ne doit pas dépasser la durée de vie des données référencées. Ou il pourrait juste prendre la propriété. – ereOn

Répondre

5

J'essaie de suivre cette règle simple à chaque fois qu'il est possible: celui qui appelle new devrait appeler delete ainsi. Sinon, le code devient vite trop compliqué pour garder une trace de ce qui est supprimé et de ce qui ne l'est pas.

Dans votre cas, si A :: A reçoit le pointeur, il ne doit pas le supprimer. Pensez à ce cas simple:

SomeHugeClass* hugeObj = new SomeHugeClass(); 
A * a1 = new A(hugeObj); 
A * a2 = new A(hugeObj); 

classe A ne peut pas savoir qui d'autre utilise ce pointeur!

Si vous voulez la classe A pour prendre soin du pointeur, il doit créer lui-même.

Bien sûr, vous pourriez gérer les deux cas, mais cela pourrait être un surpuissant, quelque chose comme ceci:

A::A() : own_huge_object(true) { 
    m_huge_object = new SomeHugeClass(); 
} 
A::A(SomeHugeClass * huge_object) : own_huge_object(false) { 
    m_huge_object = huge_object; 
} 
A::~A() { if(own_huge_object) delete m_huge_object; } 
+1

+1 pour "Celui qui appelle nouveau devrait aussi appeler delete". Je pense que cela rend les choses plus élégantes. Cependant, je dois admettre que «Qt» ne respecte pas cette règle, mais la traite plutôt bien. – ereOn

+0

Si nous suivions cette règle de manière cohérente, nous devions supprimer 'auto_ptr'! Je pense que cela prouve que, même si elle a une certaine validité, elle peut être prise trop loin. Dans certains cas, vous * voulez * transférer la propriété. –

2

Dans votre appelant exemple devrait être responsable de la suppression huge_object parce que le constructeur pourrait jeter exception et destructor de A ne sera pas appelé. Et votre implémentation a une fuite de mémoire puisque personne n'appelle delete maintenant.

Vous pouvez utiliser shared_ptr comme suit:

class A { 
    public: 
     A(shared_ptr<SomeHugeClass> huge_object) 
      : m_huge_object(huge_object) {} 
    private: 
     shared_ptr<SomeHugeClass> m_huge_object;   
}; 

Dans ce cas, vous ne devriez pas se soucier de la suppression SomeHugeClass.

+1

Je ne suis pas d'accord quant à la responsabilité de qui il devrait être, mais je suis d'accord que l'utilisation de pointeurs intelligents est la meilleure réponse. –

+3

S'il vous plaît pas 'shared_ptr' par défaut! La propriété partagée devrait être une décision consciente. Il vaut mieux utiliser 'std :: unique_ptr' ou' boost :: scoped_ptr' si vous n'avez pas encore accès à C++ 0x. –

+0

Chaque décision doit être consciente. C'est une mauvaise idée de copier du code depuis Internet (même depuis SO). J'ai ajouté le lien, donc OP pourrait en savoir plus sur 'shared_ptr'. –

0

Pour cette ligne ci-dessous

A* foo = new A(new SomeHugeClass()); 

pas pour causer une fuite de mémoire, vous pouvez vous assurer de la mémoire libre pointée par m_huge_object dans la destructor de classe A.

La définition de la classe A peut ressembler à quelque chose comme ci-dessous:

class A { 
    public: 
    A(SomeHugeClass* huge_object) 
     : m_huge_object(huge_object) {} 
    ~A() { delete m_huge_object; } 
    private: 
    SomeHugeClass* m_huge_object;   
}; 

Je n'aime pas à propos de ce qui précède est que je préfère celui qui alloue la mémoire devrait également être le seul responsable de libérer cette mémoire. De plus, il y a le problème que Kirill a signalé. Gardez donc votre définition de la classe A, et le code peut être aussi simple que:

SomeHugeClass* hugeObj = new SomeHugeClass(); 
A* foo = new A(hugeObj); 
//some code 
delete hugeObj; 
delete foo; 
+0

Ce n'est pas bon. Il se peut que le destructeur de 'foo' fasse référence' hugeObj', donc vous venez de provoquer une exception. –

+0

@Steven Sudit: Lequel? Si vous prenez la deuxième version, c'est le même code que l'affiche en premier lieu, c'est-à-dire pas de destructeur (ce que j'ai dit). –

+0

Vous avez effectivement déclaré que, dans la deuxième version, '~ A' ne supprime pas' m_hugeObj'. Vous avez ensuite montré 'hugeObj' étant détruit * après *' foo', même si 'foo' a été construit en second. C'est très faux. J'ai donné un exemple de pourquoi il est faux: 'foo' a' m_hugeObj', qui pointe vers la même instance que 'hugeObj', et on peut s'attendre à utiliser ce pointeur pendant toute sa durée de vie. Malgré cela, vous sortez le tapis de 'foo' en supprimant' hugeObj', laissant son 'm_hugeObj' pointant vers un emplacement invalide. –

0

Ceci est plus un problème de conception. Traditionnellement, lorsque vous passez des pointeurs non-const à un constructeur, l'objet doit libérer le pointeur. Passez une référence const si vous avez l'intention de conserver une copie.

Une autre conception est quelque chose le long de ces lignes: SomeHugeClass est généralement pas énorme, mais possède une énorme quantité de mémoire (par exemple un pointeur.):

class A 
{ 
    SomeHugeClass m_; 

public: 
    A(SomeHugeClass x) { m_.swap(x); } 
}; 

Cette conception est possible si SomeHugeClass met en œuvre un système efficace échanger (permuter les pointeurs). Le constructeur fait une copie de x avant d'échanger dans m_, et si vous passez un objet temporaire au constructeur, la copie peut être (et sera habituellement) élision par le compilateur.

Notez que SomeHugeClass peut être remplacé ici par smart_pointer<SomeHugeClass> en donnant la sémantique que vous voulez lorsque vous passez smart_pointer (new SomeHugeClass()) au constructeur, car il est temporaire.

EDIT (pour plus de clarté ...): code final peut ressembler à

class A 
{ 
    smart_ptr<SHC> m_; 

public: 
    A(smart_ptr<SHC> x) { m_.swap(x); } 
}; 

qui a le comportement nécessaire lorsque vous appelez A(new SHC(...)) (pas de copie, et la suppression quand A se détruit), et quand vous appelez A(smart_ptr<SHC>(a)) (une copie de a est exécutée et libérée lorsque A est détruit).

Questions connexes