Question

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 objets vont fuir et comment éviter les fuites de mémoire?

int main()
{
    base *temp = new base();
}

Qu'en est-il du code ci-dessus? Comment éviter les fuites de mémoire après le lancement du constructeur?

Était-ce utile?

La solution

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 objets alloués dynamiquement, mais supposons que vous en avez un).

C’est une des principales raisons d’utiliser les pointeurs intelligents: dans la mesure où ils sont des objets à part entière, ils reçoivent des destructeurs appelés pendant le dépilage de la pile de l’exception et ont la possibilité de libérer de la mémoire.

Si vous utilisez quelque chose comme scoped_ptr de Boost < > modèle, votre classe pourrait ressembler davantage à:

class base{
    int a;
    scoped_ptr<int> pint;
    someclass objsomeclass;
    scoped_ptr<someclass> 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 dynamiques).

Pour résumer (et j'espère que cela répond également à la question sur le

base* temp = new base();

instruction):

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 allocations de ressources susceptibles de se produire dans la construction abandonnée de l'objet:

  1. le destructeur de l'objet en cours de construction sera non appelé.
  2. les destructeurs des objets membres contenus dans la classe de cet objet seront appelés
  3. la mémoire de l'objet en construction sera libérée.

Cela signifie que si votre objet 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:

  1. attrapez l'exception, libérez les ressources, puis rallumez-les. Cela peut être difficile à corriger et peut devenir un problème de maintenance.
  2. utilisez des objets pour gérer les durées de vie des ressources (RAII) et utilisez-les comme membres. Lorsque le constructeur de votre objet lève une exception, les objets membres auront des destructeurs appelés et auront la possibilité de libérer la ressource dont ils sont responsables de la durée de vie.

Autres conseils

Les deux nouveautés seront divulguées.

Attribuez l'adresse des objets créés par le tas à des noms nommés , de manière à ce qu'ils soient supprimés à l'intérieur du destructeur des pointeurs intelligents qui reçoit un appel lorsque l'exception est levée - ( RAII ).

class base {
    int a;
    boost::shared_ptr<int> pint;
    someclass objsomeclass;
    boost::shared_ptr<someclass> psomeclass;

    base() :
        objsomeclass( someclass() ),
        boost::shared_ptr<someclass> psomeclass( new someclass() ),
        boost::shared_ptr<int> pint( new int() )
    {
        throw "constructor failed";
        a = 43;
    }
};

Maintenant psomeclass & amp; Les destructeurs pint seront appelés lorsque la pile se déroulera 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 l'allocation de mémoire ordinaire utilisant new (sans engagement) 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 objet qui vient d'être alloué, pas dans d'autres cas. De plus, la mémoire libérée est celle allouée pour les membres de l'objet, pas la mémoire allouée dans le constructeur. C'est-à-dire que la mémoire des variables membres a , pint , objsomeclass et psomeclass serait libérée, mais pas la mémoire allouée à partir de new someclass () et new int () .

Je pense que la première réponse est fausse et qu'il resterait 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 en fait la démonstration.

#include <stdio.h>


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 (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 une & "; (mais même dans ce cas, vous devez coder soigneusement la destruction de votre objet partiellement initialisé).

La bonne approche de votre problème serait alors la suivante:

#include <stdio.h>


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 objets 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<someclass> a1;
    std::shared_ptr<someclass> a2;

public:
    C()
    {
        std::shared_ptr<someclass> new_a1(new someclass());
        std::shared_ptr<someclass> 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'héritage ou jetez un destructeur, vous ne devriez vraiment pas l'être. Le comportement est étrange (mon standard n’est pas à portée de main, mais il est peut-être indéfini?).

Oui, ce code perdra de la mémoire. Blocs de mémoire alloués avec & Quot; new & Quot; 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é ou vous allez provoquer une fuite de mémoire. Donc, ces deux lignes:

psomeclass = new someclass();
pint = new int(); 

entraînera des fuites de mémoire, car vous devez effectuer les actions suivantes:

delete pint;
delete psomeclass;

Dans un bloc final pour éviter les fuites.

En outre, cette ligne:

base temp = base();

Est inutile. Vous devez juste faire:

base temp;

Ajout du " = base () " est inutile.

vous devez supprimer psomeclass ... Il n'est pas nécessaire de nettoyer le nombre entier ...

RWendi

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top