2012-03-13 2 views
2

Je veux mieux comprendre comment implémenter l'idiome RAII avec mes classes, à travers un exemple: Quelle est la méthode recommandée pour s'assurer que les pointeurs sont libres() correctement dans ma classe?RAII - Pointeurs de classe et étendue

J'ai une classe qui devrait exister pour la durée du programme. Dans l'esprit de RAII et parce que j'ai besoin de passer une référence à cette classe à d'autres classes, je la tiens dans un shared_ptr (pas sûr qu'il faut vraiment la contenir dans un shared_ptr, mais pour le fun, ça l'est).

Dans la classe ctor, j'utilise 2 buffers (pointeurs), puis boucle plusieurs fois malloc(), en utilisant le buffer, puis free() 'ing. Le détecteur doit contenir un code de sécurité pour libérer les tampons en cas de problème.

La seule façon dont le dtor peut voir les tampons est si je les déclare comme variables de classe, mais ils ne sont utilisés que dans la classe ctor.

Exemple:

class Input 
{ 
private: 
    PSOMETYPE buffer1; 
public: 
    Input(); 
    ~Input(); 
} 

Input::Input() : buffer1(NULL) 
{ 
    for(blahblah) 
    { 
     buffer1 = (PSOMETYPE)malloc(sizeof(SOMETYPE)); 
     // Do work w/buffer1 
     if(buffer1 != NULL) { free(buffer1); buffer1 = NULL } 
    } 
} 

Input::~Input() 
{ 
    if(buffer1 != NULL) { free(buffer1); buffer1 = NULL } 
} 

Vu que je n'utilise que le tampon dans le cteur, est-il logique de le déclarer comme une variable de classe privée? Si je le déclare dans le cadre du ctor, le dtor n'aura aucune connaissance de ce que c'est de libérer. Je sais que c'est un exemple trivial, et honnêtement, je pourrais implémenter cela en oubliant facilement d'utiliser un pointeur intelligent pour référencer ma classe et avoir un dteur vide, juste free() comme je le fais dans la boucle . Je n'ai pas de mentor ou de scolarité, et je ne sais pas quand l'idiome RAII devrait être suivi.

+3

Ceci est un mélange étrange d'idées C et C++ ... Y a-t-il une raison pour laquelle vous utilisez malloc au lieu de nouveaux, ou même mieux, STL templated conteneurs? Ceux-ci correspondent beaucoup mieux avec le concept de RAII – tmpearce

+0

@tmpearce: Cela est dû à mon inexpérience et l'utilisation de MSDN comme une ressource d'apprentissage. J'apprécie les références pour les alternatives/améliorations. – Lokked

Répondre

6

L'esprit de RAII serait d'utiliser un objet local pour gérer l'objet alloué localement, plutôt que de lier artificiellement sa durée de vie à l'objet construit:

class Input 
{ 
    // no pointer at all, if it's only needed in the constructor 
public: 
    Input(); 
    // no explicit destructor, since there's nothing to explicitly destroy 
}; 

Input::Input() 
{ 
    for(blahblah) 
    { 
     std::unique_ptr<SOMETYPE> buffer1(new SOMETYPE); 

     // or, unless SOMETYPE is huge, create a local object instead: 
     SOMETYPE buffer1; 

     // Do work w/buffer1 
    } // memory released automatically here 
} 

Vous ne devriez jamais avoir à utiliser delete (ou free, ou autre) vous-même si vous écrivez une classe dont le but est de gérer cette ressource - et généralement il existe déjà une classe standard (comme un pointeur intelligent ou un conteneur) qui fait ce que vous voulez. Lorsque vous avez besoin d'écrire votre propre classe de gestion, souvenez-vous toujours du Rule of Three: si votre destructeur supprime quelque chose, alors le comportement de copie par défaut de la classe provoquera presque certainement une double suppression, vous devez donc déclarer un constructeur de copie et un opérateur d'attribution de copie pour empêcher cela. Par exemple, avec votre classe je pouvais écrire le code incorrect suivant:

{ 
    Input i1;  // allocates a buffer, holds a pointer to it 
    Input i2(i1); // copies the pointer to the same buffer 
}     // BOOM! destroys both objects, freeing the buffer twice 

La façon la plus simple d'éviter cela est de supprimer les opérations de copie, de sorte que le code comme ça ne parviendra pas à compiler:

class Input { 
    Input(Input const&) = delete; // no copy constructor 
    void operator=(Input) = delete; // no copy assignment 
}; 

Les compilateurs plus anciens ne peuvent pas prendre en charge = delete; Dans ce cas, vous pouvez obtenir presque le même effet en les déclarant en privé sans = delete, et ne pas les implémenter.

+0

Merci. Cela a du sens pour moi. Votre première phrase résume parfaitement les choses. Dans votre exemple de code incorrect, une exception serait-elle générée en raison de la copie d'un unique_ptr?J'aurais certainement besoin de mettre en œuvre l'opérateur de copie et d'affectation de copie. Merci encore! – Lokked

+0

@Lokked: Si la classe contenait un 'unique_ptr', alors vous obtiendriez une erreur de compilation si vous essayiez de la copier. Vous pouvez à la place utiliser 'shared_ptr' pour obtenir une copie de pointeur sécurisée ou incorporer un objet plutôt qu'un pointeur pour obtenir la copie d'un objet. Dans mon exemple, le 'unique_ptr' ne serait pas du tout un membre, puisqu'il n'est nécessaire que dans le constructeur. –