4 votes

Bon usage du destructeur

Je viens de commencer à travailler avec C++ et j'ai maintenant une question très basique.

J'ai écrit 2 classes:

Coordinate:

#include 

class Coordinate {
private:
    int x;
    int y;
public:
    Coordinate(int a, int b) {
        x = a;
        y = b;
    };
    void printTest() {
        printf("%d %d\n", x, y);
    };
};

Test:

class Test {
private:
    int q;
    Coordinate *point;
public:
    Test(int a, int b, int c) {
        q = a;
        point = new Coordinate(b, c);
    };
    virtual ~Test() {
        delete point;
    }
};

Fonction principale:

int main() {
    Test *test = new Test(1, 2, 3);
    // ...
    delete test;
    return 0;
}

Dans mon main, j'ai travaillé avec un objet de la classe Test. J'ai écrit mon propre destructeur Test mais je ne suis pas sûr que ce destructeur fonctionne comme prévu. Est-ce qu'il libère complètement la mémoire de test? Ou dois-je faire quelque chose avec l'attribut q pour le libérer?

9voto

Toby Speight Points 3930

Ce que vous avez fait est correct, dans la mesure du possible. Valgrind rapporte

==28151== BILAN DE LA MÉMOIRE HEAP :
==28151==     en utilisation à la sortie: 0 octets dans 0 blocs
==28151==   utilisation totale de la mémoire heap : 3 allocations, 3 libérations, 72 736 octets alloués
==28151== 
==28151== Tous les blocs de mémoire heap ont été libérés -- aucune fuite n'est possible

Ce qui vous manque, c'est que le compilateur vous a fourni un constructeur de copie par défaut et un opérateur d'assignation. Ces derniers copient le pointeur, plutôt que créer une nouvelle valeur pointée, donc chaque fois que vous copiez un objet Test vous vous retrouverez avec deux objets dont les destructeurs essaieront tous deux de supprimer le même espace mémoire. C'est une double-libération, et cela peut ruiner votre journée.

Pour éviter cela, les programmeurs en C++ utilisent la _Règle des Trois_ ou la Règle des Cinq lors de l'écriture de classes, ou - encore mieux - la _Règle du Zéro_, qui dit que vous ne devriez pas faire de new ou de delete sauf dans une classe qui existe uniquement pour posséder l'espace mémoire.

4voto

Santiago Varela Points 1954

Oui, c'est l'utilisation correcte d'un destructeur C++ (votre destructeur dans Test n'a pas besoin du mot-clé virtual car il n'y a pas d'héritage dans votre exemple).

En règle générale, chaque new devrait être suivi d'un delete, mais lorsque vous utilisez une classe et une instantiation, cela devient plus subtil que cela. En suivant la Règle des Trois ou Cinq, lorsque une classe utilise de la mémoire dynamique, vous devriez redéfinir le destructeur de la classe pour libérer la mémoire comme il se doit, ce que vous avez fait, donc super !

Dans l'exécution de votre programme, lorsque delete test est invoqué, il désallouera d'abord la mémoire dynamique de point, avant de désallouer la mémoire dynamique que vous avez définie dans votre fonction principale avec votre attribut test. De cette façon, vous ne libérez pas de mémoire (youhou !) et votre gestion de mémoire a été faite correctement :)

1voto

Rama Points 2869

Vous avez besoin d'un constructeur de copie pour vous assurer que la gestion de la mémoire se passe bien. Parce que les constructeurs et opérateurs d'affectation générés implicitement copient simplement tous les membres de données de la classe ("copie superficielle"). Et puisque vous avez des pointeurs dans votre classe avec des données allouées, vous en avez vraiment besoin.

Par exemple, si dans votre partie du code principal : // ..., vous faites une copie comme:

Test testB = *test;

testB a un pointeur Coordinate qui pointe vers la même zone mémoire que *test. Cela pourrait causer des problèmes, par exemple, lorsque testB sortira de la portée, il libérera la même mémoire que *test utilise.

Le constructeur de copie devrait ressembler à ceci:

Test(const Test& other)
    : point (new Coordinate(other.x, other.y))
    , q(other.q)
{

}

Avec cela, vous pouvez être assuré que chaque Coordinate* est initialisé correctement et libéré correctement.

0voto

Jarod42 Points 15729

Dans votre cas, vous n'avez pas besoin de pointeur

class Test {
private:
    int q;
    Coordinate point;
public:
    Test(int a, int b, int c) : q(a), point(b, c) {};
};

int main() {
    Test test(1, 2, 3);
    // ...
    return 0;
}

Suffirait.

Si vous voulez allouer de la mémoire, je vous suggère vivement d'utiliser un smart pointer ou un conteneur à la place :

class Test {
private:
    int q;
    std::unique_ptr point;
public:
    Test(int a, int b, int c) : q(a), point(std::make_unique_ptr(b, c)) {};
};

int main() {
    auto test = std::make_unique(1, 2, 3);
    // ...
    return 0;
}

De cette manière, vous respectez la règle du 3/5/0.

Dans le second cas, vous voudrez probablement fournir un constructeur de copie à votre classe :

Test(const Test& rhs) : q(rhs.q), point(std::make_unique(*rhs.point)) {}

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