2 votes

Meilleure pratique pour les références scopées Idiom ?

Je viens de me faire griller par un bogue qui est en partie dû à mon manque de compréhension, et en partie dû à ce que je pense être une conception sous-optimale dans notre base de code. Je suis curieux de savoir comment ma solution de 5 minutes peut être améliorée.

Nous utilisons des objets de type ref-count, 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 courante pour obtenir une instance de ces objets (GetExisting) cache un AddRef() en son sein sans le signaler. Cela nécessite de faire un Release à la fin du bloc fonctionnel pour libérer la référence cachée, mais un développeur qui n'a pas inspecté l'implémentation de GetExisting() ne le sait pas, et quelqu'un qui oublie d'ajouter un Release à la fin de la fonction (disons, pendant une course folle de correction de bogues) perd des objets. Ceci, bien sûr, était ma brûlure.

void SomeFunction(ProgramStateInfo *P)
{
   ThreadClass *thread = ThreadClass::GetExisting( P );
   // some code goes here
   bool result = UseThreadSomehow(thread);
   // some code goes here
   thread->Release();  // Need to do this because GetExisting() calls AddRef()
}

J'ai donc écrit une petite classe pour éviter le recours à la fonction 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; }
};

Pour que maintenant je puisse juste faire ça :

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, c'est que pour accéder au pointeur de fil, je dois appeler une fonction membre de ThreadContainer, Thread(). Existe-t-il un moyen astucieux de nettoyer cela afin que la syntaxe soit plus jolie, ou est-ce que cela obscurcirait la signification du conteneur et introduirait de nouveaux problèmes pour les développeurs qui ne connaissent pas le code ?

Merci.

10voto

bayda Points 7454

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

7voto

mwigdahl Points 8768

Oui, vous pouvez mettre en œuvre operator ->() pour la classe, qui appellera récursivement operator ->() sur ce que vous rendez :

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 s'agit en fait d'utiliser la sémantique du pointeur intelligent pour votre classe d'enveloppe :

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

Vous pourriez également écrire une fonction de conversion pour permettre à votre UseThreadSomehow(ThreadContainer tc) de manière similaire.

Si Boost est une option, je pense que vous pouvez mettre en place une shared_ptr pour servir de référence intelligente également.

4voto

rlbond Points 24215

Jetez un coup d'œil à ScopeGuard . Il permet une syntaxe comme celle-ci (volée sans vergogne à partir 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
}

2voto

Je recommande de suivre bb conseils et en utilisant boost::shared_ptr<>. Si boost n'est pas une option, vous pouvez jeter un coup d'œil à std::auto_ptr<>, qui est simple et répond probablement à la plupart de vos besoins. Prenez en considération que le std::auto_ptr a une sémantique de déplacement spéciale que vous ne voulez probablement pas imiter.

L'approche consiste à fournir les opérateurs * et -> ainsi qu'un getter (pour le pointeur brut) et une opération de libération dans le cas où vous voulez libérer le contrôle de l'objet interne.

1voto

Mark Ransom Points 132545

Vous pouvez ajouter un opérateur automatique de type-cast 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ée pratique. Il pourrait y avoir quelques surprises désagréables à découvrir avec cette méthode, comme à chaque fois que vous avez une conversion implicite, mais je n'en ai pas 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!!!
}

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