2009-03-23 9 views
2

Je viens de me faire brûler par un bug qui est en partie dû à mon manque de compréhension, et en partie à cause de ce que je pense être un design sous-optimal dans notre base de code. Je suis curieux de savoir comment ma solution de 5 minutes peut être améliorée.Meilleure pratique pour l'idiome de référence scopé?

Nous utilisons des objets comptés par ref, où nous avons AddRef() et Release() sur les objets de ces classes. Un objet particulier est dérivé de l'objet ref-count, mais une fonction commune pour obtenir une instance de ces objets (GetExisting) cache un AddRef() en lui-même sans annoncer qu'il le fait. Cela nécessite de faire une Release à la fin du bloc fonctionnel pour libérer l'objet caché, mais un développeur qui n'a pas inspecté l'implémentation de GetExisting() ne le saurait pas, et quelqu'un qui oublie d'ajouter une Release à la fin de la fonction (disons, au cours d'une course folle de temps de crunch de correction de bugs) fuit des objets. Ceci, bien sûr, était ma brûlure. J'ai donc écrit une petite classe pour éviter la nécessité de Release() à la fin de ces fonctions.


class ThreadContainer 
{ 
private: 
    ThreadClass *m_T; 
public: 
    ThreadContainer(Thread *T){ m_T = T; } 
    ~ThreadContainer() { if(m_T) m_T->Release(); } 
    ThreadClass * Thread() const { return m_T; } 
}; 

Alors que maintenant je peux faire ceci:


void SomeFunction(ProgramStateInfo *P) 
{ 
    ThreadContainer ThreadC(ThreadClass::GetExisting(P)); 
    // some code goes here 
    bool result = UseThreadSomehow(ThreadC.Thread()); 
    // some code goes here 
    // Automagic Release() in ThreadC Destructor!!! 
} 

Ce que je n'aime pas est que pour accéder au pointeur de fil, je dois appeler une fonction de membre de ThreadContainer, Fil() . Y a-t-il un moyen astucieux de nettoyer cela pour qu'il soit syntaxiquement plus joli, ou est-ce que quelque chose comme ça obscurcirait la signification du conteneur et introduirait de nouveaux problèmes pour les développeurs qui ne connaissent pas le code?

Merci.

Répondre

10

boost :: utilisation shared_ptr il est possible de définir votre propre fonction destructor, comme nous dans l'exemple suivant: http://www.boost.org/doc/libs/1_38_0/libs/smart_ptr/sp_techniques.html#com

+0

La réponse de Mark Ransom était ce que je voulais, mais votre réponse me pousse vers une bonne direction d'apprentissage de nouvelles des trucs et une solution de niveau supérieur au problème global, donc je vais le marquer comme "La" réponse. Merci. –

+0

Content d'entendre. Vous êtes les bienvenus. Il est vraiment préférable d'utiliser une classe de genre pour toutes les choses similaires à la place d'écrire une solution séparée pour chacun d'eux. – bayda

4

Jetez un oeil à ScopeGuard. Il permet une syntaxe comme ceci (sans vergogne volée de ce lien):

{ 
    FILE* topSecret = fopen("cia.txt"); 
    ON_BLOCK_EXIT(std::fclose, topSecret); 
    ... use topSecret ... 
} // topSecret automagically closed 

Ou vous pouvez essayer Boost::ScopeExit:

void World::addPerson(Person const& aPerson) { 
    bool commit = false; 
    m_persons.push_back(aPerson); // (1) direct action 
    BOOST_SCOPE_EXIT((&commit)(&m_persons)) 
    { 
     if(!commit) 
      m_persons.pop_back(); // (2) rollback action 
    } BOOST_SCOPE_EXIT_END 

    // ...      // (3) other operations 

    commit = true;    // (4) turn all rollback actions into no-op 
} 
7

Oui, vous pouvez mettre en œuvre operator ->() pour la classe, qui appellera récursive operator ->() sur tout retour:

class ThreadContainer 
{ 
private: 
    ThreadClass *m_T; 
public: 
    ThreadContainer(Thread *T){ m_T = T; } 
    ~ThreadContainer() { if(m_T) m_T->Release(); } 
    ThreadClass * operator ->() const { return m_T; } 
}; 

Il utilise efficacement la sémantique de pointeur intelligent pour votre classe d'emballage:

Thread *t = new Thread(); 
... 
ThreadContainer tc(t); 
... 
tc->SomeThreadFunction(); // invokes tc->t->SomeThreadFunction() behind the scenes... 

Vous pouvez également écrire une fonction de conversion pour activer vos appels de type UseThreadSomehow(ThreadContainer tc) d'une manière similaire.

Si Boost est une option, je pense que vous pouvez également configurer un shared_ptr comme référence intelligente.

+0

À quoi cela ressemble-t-il en utilisation? –

+0

Ah, je comprends. Cela appelle des fonctions sur le membre caché (ce qui est utile, donc merci), mais je suppose que ce que je veux, c'est un moyen plus simple de passer les fonctions INTO membres cachés, peut-être en aliasant la classe conteneur comme classe cachée. Je suppose que je pourrais surcharger() et avoir ce retour m_T ... –

+0

Non, cela ne fonctionnerait pas. Vous aurez besoin d'une conversion automatique de ThreadContainer en Thread *, et c'est possible, mais cela peut être compliqué. Vraiment, je suggère de regarder la suggestion Boost de BB ci-dessous si vous êtes en mesure d'utiliser Boost. shared_ptr ressemble exactement à ce que vous cherchez. – mwigdahl

1

Vous pouvez ajouter un opérateur de type cast automatique pour retourner votre pointeur brut. Cette approche est utilisée par la classe CString de Microsoft pour donner un accès facile au tampon de caractères sous-jacent, et je l'ai toujours trouvé utile. Il peut y avoir des surprises désagréables à découvrir avec cette méthode, comme à chaque fois que vous avez une conversion implicite, mais je n'en ai jamais rencontré.

class ThreadContainer 
{ 
private: 
    ThreadClass *m_T; 
public: 
    ThreadContainer(Thread *T){ m_T = T; } 
    ~ThreadContainer() { if(m_T) m_T->Release(); } 
    operator ThreadClass *() const { return m_T; } 
}; 

void SomeFunction(ProgramStateInfo *P) 
{ 
    ThreadContainer ThreadC(ThreadClass::GetExisting(P)); 
    // some code goes here 
    bool result = UseThreadSomehow(ThreadC); 
    // some code goes here 
    // Automagic Release() in ThreadC Destructor!!! 
} 
+0

Ahhh, c'est un peu ce que je cherchais, je ne savais pas trop comment le faire. –

2

Je recommande la suite bb advice et en utilisant boost :: shared_ptr <>. Si boost n'est pas une option, vous pouvez jeter un oeil à std :: auto_ptr <>, ce qui est simple et répond probablement à la plupart de vos besoins. Prenez en compte le fait que std :: auto_ptr a une sémantique de mouvement spéciale que vous ne voulez probablement pas imiter.

L'approche fournit à la fois les opérateurs * et -> avec un getter (pour le pointeur brut) et une opération de libération au cas où vous voulez libérer le contrôle de l'objet interne.

Questions connexes