class someclass {}; class base { int a; int *pint; someclass objsomeclass; someclass* psomeclass; public: base() { objsomeclass = someclass(); psomeclass = new someclass(); pint = new int(); throw "constructor failed"; a = 43; } } int main() { base temp(); }
Dans le code ci-dessus, le constructeur jette. Quels objects vont fuir et comment éviter les memory leaks?
int main() { base *temp = new base(); }
Que diriez-vous du code ci-dessus? Comment éviter les memory leaks après le lancement du constructeur?
Oui, il y aura une fuite de mémoire. Lorsque le constructeur lance, aucun destructeur ne sera appelé (dans ce cas, vous ne montrez pas de destructeur qui libère les objects alloués dynamicment, mais supposons que vous en avez un).
C’est une raison majeure pour utiliser des pointeurs intelligents – étant donné que les pointeurs intelligents sont des objects à part entière, ils obtiennent des destructeurs appelés pendant le dépilage de la stack de l’exception et ont la possibilité de libérer de la mémoire.
Si vous utilisez quelque chose comme le modèle scoped_ptr <> de Boost, votre classe pourrait ressembler davantage à:
class base{ int a; scoped_ptr pint; someclass objsomeclass; scoped_ptr psomeclass; base() : pint( new int), objsomeclass( someclass()), psomeclass( new someclass()) { throw "constructor failed"; a = 43; } }
Et vous n’auriez aucune fuite de mémoire (et le serveur par défaut nettoyerait également les allocations de mémoire dynamics).
Pour résumer (et j’espère que cela répond aussi à la question sur le
base* temp = new base();
déclaration):
Lorsqu’une exception est générée à l’intérieur d’un constructeur, vous devez prendre en compte plusieurs éléments pour gérer correctement les affectations de ressources pouvant avoir eu lieu dans la construction abandonnée de l’object:
Cela signifie que si votre object possède des ressources, vous avez 2 méthodes disponibles pour nettoyer ces ressources qui ont peut-être déjà été acquises lorsque le constructeur lance:
Les deux nouvelles seront fuites.
Atsortingbuez l’adresse des objects créés par le tas à des pointeurs intelligents nommés, de manière à ce qu’ils soient supprimés à l’intérieur du destructeur de pointeurs intelligents qui reçoit un appel lorsque l’exception est levée ( RAII ).
class base { int a; boost::shared_ptr pint; someclass objsomeclass; boost::shared_ptr psomeclass; base() : objsomeclass( someclass() ), boost::shared_ptr psomeclass( new someclass() ), boost::shared_ptr pint( new int() ) { throw "constructor failed"; a = 43; } };
Désormais, les destructeurs psomeclass & pint seront appelés lorsque la stack sera déroulée lorsque l’exception sera levée dans le constructeur, et ces destructeurs désalloueront la mémoire allouée.
int main(){ base *temp = new base(); }
Pour une allocation de mémoire ordinaire utilisant new (non implicitement) new, la mémoire allouée par l’opérateur new est automatiquement libérée si le constructeur lève une exception. En ce qui concerne la libération des membres individuels (en réponse aux commentaires sur la réponse de Mike B), la libération automatique ne s’applique que lorsqu’une exception est levée dans le constructeur d’un object nouvellement alloué, et non dans d’autres cas. De plus, la mémoire libérée est celle allouée pour les membres de l’object, pas la mémoire allouée dans le constructeur. En d’autres termes , la mémoire des variables membres a , pint , objsomeclass et psomeclass serait libérée , mais pas la mémoire allouée par new someclass () et new int () .
Je crois que la première réponse est fausse et qu’il y aurait encore une fuite de mémoire. Le destructeur des membres de la classe ne sera pas appelé si le constructeur lève une exception (car il n’a jamais terminé son initialisation et certains membres n’ont peut-être jamais atteint leurs appels de constructeur). Leurs destructeurs ne sont appelés que pendant l’appel de destructeur de la classe. Cela n’a de sens que.
Ce programme simple le démontre.
#include class A { int x; public: A(int x) : x(x) { printf("A constructor [%d]\n", x); } ~A() { printf("A destructor [%d]\n", x); } }; class B { A a1; A a2; public: B() : a1(3), a2(5) { printf("B constructor\n"); throw "failed"; } ~B() { printf("B destructor\n"); } }; int main() { B b; return 0; }
Avec la sortie suivante (en utilisant g ++ 4.5.2):
A constructor [3] A constructor [5] B constructor terminate called after throwing an instance of 'char const*' Aborted
Si votre constructeur échoue à mi-parcours, il vous incombe de le gérer. Pire encore, l’exception peut être levée du constructeur de votre classe de base! La manière de traiter ces cas consiste à utiliser un “bloc de tentative de fonction” (mais même dans ce cas, vous devez coder soigneusement la destruction de votre object partiellement initialisé).
L’approche correcte de votre problème serait alors quelque chose comme ceci:
#include class A { int x; public: A(int x) : x(x) { printf("A constructor [%d]\n", x); } ~A() { printf("A destructor [%d]\n", x); } }; class B { A * a1; A * a2; public: B() try // <--- Notice this change : a1(NULL), a2(NULL) { printf("B constructor\n"); a1 = new A(3); throw "fail"; a2 = new A(5); } catch ( ... ) { // <--- Notice this change printf("B Cleanup\n"); delete a2; // It's ok if it's NULL. delete a1; // It's ok if it's NULL. } ~B() { printf("B destructor\n"); } }; int main() { B b; return 0; }
Si vous l'exécutez, vous obtiendrez le résultat attendu dans lequel seuls les objects alloués sont détruits et libérés.
B constructor A constructor [3] B Cleanup A destructor [3] terminate called after throwing an instance of 'char const*' Aborted
Vous pouvez toujours vous en servir avec des pointeurs partagés intelligents si vous le souhaitez, avec une copie supplémentaire. Ecrire un constructeur similaire à ceci:
class C { std::shared_ptr a1; std::shared_ptr a2; public: C() { std::shared_ptr new_a1(new someclass()); std::shared_ptr new_a2(new someclass()); // You will reach here only if both allocations succeeded. Exception will free them both since they were allocated as automatic variables on the stack. a1 = new_a1; a2 = new_a2; } }
Bonne chance, Tzvi.
Si vous ajoutez un constructeur, vous devez nettoyer tout ce qui est arrivé avant l’appel à lancer. Si vous utilisez l’inheritance ou jetez un destructeur, vous ne devriez vraiment pas l’être. Le comportement est étrange (mon standard n’est pas à scope de main, mais il est peut-être indéfini?).
Oui, ce code perdra de la mémoire. Les blocs de mémoire alloués avec “new” ne sont pas libérés lorsqu’une exception est déclenchée. Cela fait partie de la motivation derrière RAII .
Pour éviter la fuite de mémoire, essayez ce qui suit:
psomeclass = NULL; pint = NULL; /* So on for any pointers you allocate */ try { objsomeclass = someclass(); psomeclass = new someclass(); pint = new int(); throw "constructor failed"; a = 43; } catch (...) { delete psomeclass; delete pint; throw; }
Tout ce que vous “nouveau” doit être supprimé, sinon vous causerez une fuite de mémoire. Donc, ces deux lignes:
psomeclass = new someclass(); pint = new int();
Provoquera des memory leaks, car vous devez faire:
delete pint; delete psomeclass;
Dans un bloc enfin pour éviter les fuites.
Aussi, cette ligne:
base temp = base();
Est inutile. Vous devez juste faire:
base temp;
L’ajout du “= base ()” n’est pas nécessaire.
vous devez supprimer psomeclass … Il n’est pas nécessaire de nettoyer l’entier …
RWendi