Question

Quelle est la meilleure approche pour encapsuler des objets et gérer leur durée de vie? Exemple: j'ai une classe A , qui contient un objet de type B et en est l'unique responsable .

Solution 1 , clonez un objet b pour vous assurer que seul A est en mesure de le nettoyer.

class A
{
    B *b;
public:
    A(B &b)
    {
        this->b = b.clone();
    }

    ~A()
    {
        delete b; // safe
    }
};

Solution 2 , utilisez directement l'objet transmis, vous risquez un potentiel de double libre ici.

class A
{
    B *b;
public:
    A(B *b)
    {
        this->b = b;
    }

    ~A()
    {
        delete b; // unsafe
    }
};

Dans mon cas, la solution n ° 2 conviendrait le mieux. Cependant, je me demande si cela est considéré comme un code incorrect, car quelqu'un pourrait ne pas connaître le comportement de A , même s'il est documenté. Je peux penser à ces scénarios:

B *myB = new B();
A *myA = new A(myB);
delete myB; // myA contains a wild pointer now

Ou,

B *myB = new B();
A *firstA = new A(myB);
A *secondA = new A(myB); // bug! double assignment
delete firstA; // deletes myB, secondA contains a wild pointer now
delete secondA; // deletes myB again, double free

Puis-je simplement ignorer ces problèmes si je documente correctement le comportement de A? Suffit-il de déclarer la responsabilité et de laisser aux autres le soin de lire les documents? Comment cela est-il géré dans votre base de code?

Était-ce utile?

La solution

Vous devez définir votre objet de sorte que la sémantique de propriété soit, autant que possible, définie par l'interface. Comme David Thornley l'a souligné, std :: auto_ptr est le pointeur intelligent de choix pour indiquer le transfert de propriété . Définissez votre classe comme suit:

class A
{    
    std::auto_ptr<B> b;
public:    
    A(std::auto_ptr<B> b)    
    {
        this->b = b;
    }
    // Don't need to define this for this scenario
    //~A()
    //{ 
    //   delete b; // safe
    //}
};

Puisque le contrat de std :: auto_ptr est que cession = transfert de propriété, votre constructeur indique maintenant implicitement qu'un objet A possède la propriété du pointeur sur B il est passé. En fait, si un client tente de faire quelque chose avec std :: auto_ptr & Lt; B & Gt; qu’ils utilisaient pour construire un A après la construction, l’opération échouera, car le pointeur qu’ils détiennent sera invalide.

Autres conseils

Je ne supprime jamais rien moi-même, sauf obligation absolue. Cela conduit à des erreurs.

Les pointeurs intelligents sont vos amis. std::auto_ptr<> est votre ami quand un objet en possède un autre et qu'il est responsable de le supprimer lorsqu'il sort de la portée. boost::shared_ptr<> (ou maintenant, std::tr1::shared_ptr<>) est votre ami lorsqu'il y a potentiellement plus d'un objet attaché à un autre objet et que vous voulez que l'objet soit supprimé lorsqu'il n'y a plus de référence.

Vous devez donc utiliser votre solution 1 avec auto_ptr ou votre solution 2 avec shared_ptr.

Si vous écrivez du code que quelqu'un d'autre utilisera plus tard, ces problèmes doivent être résolus. Dans ce cas, je choisirais un comptage de références simple (peut-être avec des pointeurs intelligents). Prenons l'exemple suivant:

Lorsqu'un objet B est attribué à une instance de la classe encapsulante, elle appelle une méthode pour augmenter le compteur de références B de l'objet. Lorsque la classe d'encapsulation est détruite, elle ne supprime pas B, mais appelle une méthode pour réduire le nombre de références. Lorsque le compteur atteint zéro, l'objet B est détruit (ou se détruit lui-même). De cette manière, plusieurs instances de la classe encapsulant peuvent fonctionner avec une seule instance de l’objet B.

Pour en savoir plus sur le sujet: Comptage de références .

Si votre objet est seul responsable de l'objet transmis, sa suppression doit être sécurisée. S'il n'est pas sûr que l'affirmation que vous êtes seul responsable est fausse. Alors c'est quoi? Si votre interface indique que vous supprimerez l'objet entrant, il incombera à l'appelant de s'assurer que vous recevez un objet que vous devez supprimer.

Si vous clonez A et que A1 et A2 conservent les références à B, la durée de vie de B n’est pas entièrement contrôlée par A. Elle est partagée entre les divers A. La clonage B assure une relation directe entre Au fur et à mesure, il sera facile d’assurer la cohérence de la vie.

Si le clonage de B n’est pas une option, vous devez abandonner le concept selon lequel A est responsable de la durée de vie de B. Soit un autre objet doit gérer les différents B, soit vous devez implémenter une méthode telle que le comptage de références.

Pour référence, quand je pense au terme "clone", cela implique une copie en profondeur, qui clonerait également B. Je m'attendrais à ce que les deux As soient complètement détachés l'un de l'autre après un clone.

Je ne clone pas des choses inutilement ou & "juste pour être en sécurité &".

Au lieu de cela, je sais à qui revient la responsabilité de supprimer quelque chose: soit par la documentation, soit par des pointeurs intelligents ... par exemple, si j’ai une fonction create qui instancie quelque chose et renvoie un pointeur sur elle sans la supprimer. afin de ne pas savoir exactement où et par qui cette chose est supposée être supprimée, alors, au lieu de <=> renvoyer un pointeur nu, je pourrais définir le type de retour de <=> comme renvoyant le pointeur contenu dans une sorte de pointeur intelligent.

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