2010-07-28 15 views
1

Considérons la structure de classe suivante:Comment gérer différentes stratégies de propriété pour un membre de pointeur?

class Filter 
{ 
    virtual void filter() = 0; 
    virtual ~Filter() { } 
}; 

class FilterChain : public Filter 
{ 
    FilterChain(collection<Filter*> filters) 
    { 
     // copies "filters" to some internal list 
     // (the pointers are copied, not the filters themselves) 
    } 

    ~FilterChain() 
    { 
     // What do I do here? 
    } 

    void filter() 
    { 
     // execute filters in sequence 
    } 
}; 

J'expose la classe dans une bibliothèque, donc je n'ai pas le contrôle sur la façon dont il sera utilisé. Je rencontre actuellement quelques problèmes de conception concernant la propriété des objets FilterFilterChain tient les pointeurs à. Plus précisément, voici deux scénarios d'utilisation possibles pour FilterChain:

  • Scénario A: quelques-unes des fonctions dans ma bibliothèque sont la construction d'une (éventuellement complexe) chaîne de filtrage, l'allocation de mémoire si nécessaire, et le retour d'un nouveau alloué FilterChain objet. Par exemple, une de ces fonctions construit une chaîne de filtres à partir d'un fichier, qui peut décrire des filtres arbitrairement complexes (y compris des chaînes de filtres de chaînes de filtres, etc.). L'utilisateur de la fonction est responsable de la destruction de l'objet une fois le travail terminé.
  • Scénario B: l'utilisateur a accès à un groupe d'objets Filter et souhaite les combiner dans des chaînes de filtres d'une manière spécifique. L'utilisateur construit FilterChain objets pour son propre usage, puis les détruit lorsqu'il en a fini avec lui. Les objets Filterne doivent pas être être détruits lorsqu'un FilterChain les référençant est détruit.

Maintenant, les deux moyens les plus simples pour gérer la propriété dans l'objet FilterChain sont:

  • FilterChain possèdent les Filter objets. Cela signifie que les objets référencés par FilterChain sont détruits dans le destructeur FilterChain. Ce qui est incompatible avec le scénario B.
  • FilterChain ne pas posséder les objets Filter. Cela signifie que le destructeur de FilterChain ne fait rien. Maintenant, il y a un problème avec le scénario A, car l'utilisateur devrait connaître la structure interne de tous les objets impliqués afin de les détruire sans en manquer un, car le parent FilterChain ne le fait pas lui-même. C'est juste une mauvaise conception, et demander des fuites de mémoire.

Par conséquent, j'ai besoin de quelque chose de plus compliqué. Ma première supposition est de concevoir un pointeur intelligent avec un indicateur booléen réglable indiquant si oui ou non le pointeur intelligent possède l'objet. Puis, au lieu de prendre une collection de pointeurs vers Filter objets, FilterChain prendrait une collection de pointeurs intelligents à Filter objets. Lorsque le destructeur FilterChain est appelé, il détruit les pointeurs intelligents. Le destructeur du pointeur intelligent lui-même détruirait alors l'objet pointé (objet Filter) si et seulement si le drapeau booléen indiquant la propriété est défini. J'ai l'impression que ce problème est courant en C++, mais ma recherche de solutions populaires ou de modèles de conception intelligents n'a pas très bien réussi. En effet, auto_ptr n'aide pas vraiment ici et shared_ptr semble trop. Donc, ma solution est-elle une bonne idée ou pas?

+4

Pourquoi le pointeur partagé est-il trop puissant? C'est beaucoup plus facile que de rouler les vôtres en termes de mise en œuvre immédiate et de maintenance (tout le monde sait exactement de quoi il s'agit). Les frais généraux sont minimes. – Patrick

+2

Le destructeur de classe de base DOIT être virtuel. –

+0

@Patrick: ça me semble trop compliqué car un shared_ptr est un compteur de référence, et j'ai seulement besoin d'un drapeau booléen. –

Répondre

2

Les pointeurs intelligents ne sont pas surdimensionnés: il est évident que vous avez un problème de conception qui, d'une manière ou d'une autre, nécessite un examen minutieux de la durée de vie et de la propriété des objets. Cela serait particulièrement vrai si vous voulez pouvoir rajuster à nouveau les filtres dans le graphe du filtre au moment de l'exécution, ou la possibilité de créer des objets composés FilterChain. L'utilisation de shared_ptr permet de supprimer la plupart de ces problèmes d'un seul coup et de simplifier considérablement la conception. Le seul gotcha potentiel que je pense ici est si votre filtre contient des cycles. Je peux voir que cela pourrait arriver si vous avez une sorte de boucle de rétroaction. Dans ce cas, je suggérerais que tous les objets Filter appartiennent à une seule classe, puis le FilterChain stockerait des pointeurs faibles sur les objets Filter. Je parierais que le temps d'exécution des étapes de filtrage dépasserait largement le surdébit supplémentaire de déréférencement d'un pointeur intelligent. shared_ptr est conçu pour être assez léger.

+0

On dirait que tout le monde ici pense que c'est la bonne chose à faire. Très bien alors allons-y pour shared_ptr. –

+0

J'avais l'habitude d'avoir un peu de bloc mental quand il s'agissait de pointeurs intelligents, mais ayant commencé à utiliser shared_ptr beaucoup plus récemment, j'ai trouvé que cela rend la vie * tellement * beaucoup plus facile. –

0

FilterChain doit avoir une méthode distincte DeleteAll(), qui itérations sur la collection et delete s les filtres. Il est appelé dans le scénario A et n'est pas appelé dans le scénario B. Cela nécessite une certaine intelligence de la part des utilisateurs de FilterChain, mais pas plus de rappeler alors à delete et l'objet qu'ils new 'd. (Ils devraient être en mesure de gérer cela, ou ils méritent une fuite de memeory)

+2

Ils ne devraient pas avoir à se souvenir de supprimer quoi que ce soit, n'importe où. Utilisez des conteneurs, des pointeurs intelligents, etc. Soyez sûr de l'exception, soyez propres, ne gérez jamais explicitement les choses en dehors d'une classe d'utilité. – GManNickG

2

Les filtres sont si gros que vous ne pouvez pas simplement faire une copie profonde de chacun lorsque vous créez le FilterChain? Si vous étiez capable de faire cela, alors tous vos problèmes disparaissent: Le FilterChain nettoie toujours après lui-même.

Si ce n'est pas une option en raison de problèmes de mémoire, alors utiliser shared_ptr semble être le plus logique. L'appelant devra être responsable de garder un shared_ptr pour chaque objet qui l'intéresse, puis le FilterChain saura s'il faut supprimer des filtres particuliers ou non delete d.

EDIT: Comme Neil a noté Filter a besoin d'un destructeur virtuel.

+0

Les objets de filtre ne peuvent pas être copiés, non pas en raison de problèmes de mémoire, mais parce que cela entraînerait des problèmes liés à la duplication des informations d'état (il serait également sémantiquement douteux). –

+0

@ e-t172 Alors, qu'en est-il de l'état du filtre stocké dans 'shared_ptr', donc vous pouvez copier' Filter' s? –

+0

Quelle est la différence avec l'utilisation de shared_ptr dans FilterChain? –

0

Je voudrais aller avec FilterChain ne possédant pas les objets de filtre. Ensuite, dans votre bibliothèque, lorsque vous devez charger une FilterChain à partir du fichier, vous disposez d'un autre objet Loader responsable de la durée de vie des objets Filter. Ainsi, le FilterChain fonctionnerait de manière cohérente pour les chaînes chargées par la bibliothèque et les chaînes créées par l'utilisateur.

+0

Cela signifierait dupliquer la structure de filtre éventuellement complexe dans l'objet Loader, juste pour gérer la propriété. C'est trop complexe par rapport aux autres solutions. –

+0

En fait ce que vous proposez est simplement la deuxième "solution" dans ma question originale, sauf que vous déplacez la logique de gestion des objets de filtre de l'extérieur de la bibliothèque vers l'intérieur. En effet le code est plus simple pour l'utilisateur de la librairie, mais juste parce que la complexité excessive est cachée à l'intérieur de la librairie ne veut pas dire que c'est un bon design (c'est nécessaire mais pas suffisant). –

+0

Je ne vois pas pourquoi vous auriez besoin de dupliquer la structure du filtre dans le chargeur. Le chargeur aurait seulement besoin de gérer la durée de vie des filtres. L'utilisateur de la bibliothèque devrait toujours gérer la durée de vie de ses propres filtres. Les filtres doivent (dé) sérialiser eux-mêmes. Chaque fois que je vois un code comme celui-ci, c'est parce qu'il n'y a pas assez de réflexion sur la conception de la classe Filter et maintenant vous payez le prix. –