Pourquoi le destructeur par défaut C++ ne détruit-il pas mes objets?
la spécification C++ indique que le destructeur par défaut supprime tous les membres non statiques. Néanmoins, je n'arrive pas à l'atteindre.
j'ai ceci:
class N {
public:
~N() {
std::cout << "Destroying object of type N";
}
};
class M {
public:
M() {
n = new N;
}
// ~M() { //this should happen by default
// delete n;
// }
private:
N* n;
};
alors ceci devrait imprimer le message donné, mais il ne le fait pas:
M* m = new M();
delete m; //this should invoke the default destructor
13 réponses
Qu'est-ce qui vous fait penser que l'objet n
points à supprimer par défaut? Le destructeur par défaut détruit le pointeur, pas ce qu'il pointe.
Edit: je vais voir si je peux rendre cela un peu plus clair.
si vous aviez un pointeur local, et qu'il était hors de portée, vous vous attendriez à ce que l'objet qu'il pointe soit détruit?
{
Thing* t = new Thing;
// do some stuff here
// no "delete t;"
}
le pointeur t
est nettoyé, mais le Thing
il est pas. C'est une fuite. Essentiellement la même chose se passe dans votre classe.
imaginez quelque chose comme ceci:
class M {
public:
M() { }
// ~M() { // If this happens by default
// delete n; // then this will delete an arbitrary pointer!
// }
private:
N* n;
};
vous êtes sur votre propre avec des pointeurs en C++. Personne ne les supprimera automatiquement pour vous.
le destructeur par défaut détruira en effet tous les objets membres. Mais l'objet de membre dans ce cas est un pointeur lui-même, pas la chose qu'il pointe. Cela pourrait avoir confondu vous.
cependant, si au lieu d'un simple pointeur intégré, vous utiliserez un intelligent pointeur , la destruction d'un "pointeur" (qui est en fait une classe) peut déclencher la destruction de l'objet pointé.
le destructeur par défaut détruit le pointeur. Si vous voulez supprimer le N
avec le destructeur par défaut de M
, utilisez un pointeur intelligent. Remplacer N * n;
par auto_ptr<N> n;
et n
sera détruit.
Edit: comme souligné dans les commentaires, auto_ptr<>
n'est pas le meilleur pointeur intelligent pour tous les usages, mais il ressemble à ce qui est demandé ici. Il représente la propriété: le N dans M est là pour la durée de la M et pas plus. Copier ou assigner un auto_ptr<>
représente un changement de propriété, ce qui n'est généralement pas ce que vous voulez. Si vous voulez passer un pointeur de M, vous devez passer un N *
obtenu de n.get()
.
une solution plus générale serait boost::shared_ptr<>
, qui sera dans la norme C++0x. Qui peut être utilisé assez bien partout où un pointeur brut serait utilisé. Ce n'est pas la construction la plus efficace, et a des problèmes avec les références circulaires, mais il est généralement une construction sûre.
un autre edit: pour répondre à la question dans un autre commentaire, le comportement standard du destructeur par défaut est de détruire tous les membres de données et les classes de base. Cependant, supprimer un pointeur brut supprime simplement le pointeur, pas ce qu'il pointe. Après tout, l'implémentation ne peut pas savoir si c'est le seul pointeur, ou le plus important, ou quelque chose comme ça. L'idée derrière les pointeurs intelligents est que supprimer un pointeur intelligent conduira au moins à la suppression de ce qu'il désigne, ce qui est généralement le comportement souhaité.
y a-t-il une raison pour laquelle vous utilisez un pointeur lorsque l'objet pointé semble appartenir à l'objet contenu? Il suffit de stocker l'objet en valeur:
class M
{
N n;
public:
M() : n()
{
}
};
il est incorrect de dire que le destructeur supprime membres. Il invoque le destructeur de chaque membre (et classe de base), qui pour les types intégrés (comme les pointeurs) signifie Ne rien faire.
Matching nouvelle s supprimer s est de votre responsabilité (que ce soit manuellement ou avec l'aide de pointeurs intelligents).
votre argument peut sembler solide, mais ce n'est pas comme ça que les choses fonctionnent pour les pointeurs.
n
est effectivement en cours de destruction, mais, ce que cela signifie est que le N*
destructeur est appelé qui, il ne détruit pas quel que soit l'objet n
pointe. Pensez au destructeur du N*
comme si c'était un destructeur du int
. Il supprime sa valeur, la même chose se passe pour un pointeur, il supprime l'adresse qu'il désigne, mais il n'a pas besoin de supprimer un objet situé à l'adresse que vous venez de supprimer.
je pense que vous pouvez être confus au sujet des niveaux d'indirection ici. Quand une instance est détruite, chaque membre de données est effectivement détruit avec elle. Dans votre cas, quand un M
est détruit et que M::~M()
est appelé, sa variable n
est vraiment détruite. Le problème est que n
est un N *
, donc pendant que le pointeur est détruit, la chose qu'il pointe n'est pas.
delete
ne fonctionne pas comme cela. Tenir compte de votre déclaration simple:
delete n;
la déclaration ci-dessus détruit la chose que n
pointe, qui est un objet de type N
. Il ne détruit pas n
lui-même, qui est un pointeur N *
.
il y a une très bonne raison que M::~M()
n'appelle pas automatiquement delete n;
ce qui est ceci: l'objet N
auquel il est fait référence pourrait être partagé entre plusieurs objets M
, et si un M
ont été détruits, les autres perdaient le N
qu'ils pointaient du doigt, laissant partout d'horribles pointes pendantes. C++ ne tente pas d'interpréter ce que vous signifiait pour faire avec vos pointeurs, il fait juste ce que vous dit il à faire.
en bref, M
détruit vraiment tous ses membres quand il est détruit, c'est juste que cette destruction ne fait pas ce que vous pensez qu'elle devrait faire. Si vous vous voulez un type de pointeur qui prend la propriété d'un objet et le détruit quand le pointeur est détruit, regardez std::auto_ptr
.
le destructeur par défaut ressemble à ceci:
~M()
{
}
le destructeur par défaut n'insère pas de code pour faire quoi que ce soit avec des choses pointées. Et si vous aviez n pointant vers une variable de pile? L'insertion automatique d'un supprimerait n s'écraserait.
Le destructeur par défaut appelle le destructeur de chaque membre de la classe ( membre.~T () ). Pour un pointeur, c'est un no-op (ne fait rien), tout comme myint.~ int() ne fait rien, mais pour les classes de membres avec des destructeurs définis, le destructeur est appelé.
voici un autre exemple:
struct MyClass {
public:
MyClass() { .. } // doesn't matter what this does
int x;
int* p;
std::string s;
std::vector<int> v;
};
le destructeur par défaut en réalité fait ceci:
MyClass::~MyClass()
{
// Call destructor on member variables in reverse order
v.~std::vector<int>(); // frees memory
s.~std::string(); // frees memory
p.~int*(); // does nothing, no custom destructor
x.~int(); // does nothing, no custom destructor
}
bien sûr, si vous définissez un destructeur, le code dans votre destructeur s'exécute avant les variables membres sont détruites (évidemment, sinon elles ne seraient pas valable!).
essayez d'éviter d'utiliser des pointeurs. Ce sont des éléments de dernier recours.
class N {
public:
~N() {
std::cout << "Destroying object of type N";
}
};
class M {
public:
M() {
// n = new N; no need, default constructor by default
}
// ~M() { //this should happen by default
// delete n;
// }
private:
N n; // No pointer here
};
alors utilisez - le de cette façon
main(int, char**)
{
M m;
}
cela affichera détruire objet de type N
je pense que vous pourriez bénéficier d'un exemple très simple:
int main(int argc, char* argv[])
{
N* n = new N();
} // n is destructed here
cela n'imprimera rien non plus.
pourquoi ? Parce que le pointer
( n
) est détruit, pas l'objet pointé sur *n
.
bien sûr, vous ne voudriez pas qu'il détruise l'objet pointé vers:
int main(int argc, char* argv[])
{
N myObject;
{
N* n = &myObject;
} // n is destructed here, myObject is not
myObject.foo();
} // myObject is destructed here
vous devez vous rappeler que contrairement à des langues comme C#
ou Java
, il y a 2 façons de créer des objets en C++: directement N myObject
(sur la pile) ou via new
comme dans new N()
dans ce cas, l'objet est placé sur le tas et vous êtes responsable de le libérer ultérieurement.
donc votre destructeur détruit le pointeur, mais pas l'objet pointé. Attribuez l'objet Sans nouveau (et sans utiliser de pointeur) ou utilisez un Smart Pointer
si vous voulez qu'il soit automatique.
puisque vous utilisez new
pour créer une instance, elle ne sera pas supprimée par défaut.
class N {
public:
~N() {
std::cout << "Destroying object of type N";
}
};
class M {
public:
M() {
n = new N;
}
// ~M() { //this should happen by default
// delete n;
// }
private:
N* n;
};
et maintenant, l'attente est :
M* m = new M();
delete m; //this should invoke the default destructor
cela n'arrivera que si la classe M est dérivée de N:
class M: Class N {
...
seulement dans cette situation,
M* m = new M()
appellera le constructeur de N puis le constructeur de M, où comme
delete m;
appellera automatiquement destructeur de m d'abord et ensuite N