61 votes

Sous-classe/inherit conteneurs standard?

J'ai souvent lu des déclarations sur ce Débordement de Pile. Personnellement, je ne trouve aucun problème avec cela, à moins que je l'utilise dans un polymorphe façon; c'est à dire où je dois utiliser virtual destructeur.

Si je veux prolonger/ajouter la fonctionnalité d'un conteneur standard alors quelle est la meilleure façon d'hériter d'une? Habillage de ces conteneurs à l'intérieur d'une classe personnalisée nécessite beaucoup plus d'efforts et est toujours impur.

98voto

ex0du5 Points 1828

Il y a un certain nombre de raisons pour lesquelles ce une mauvaise idée.

Tout d'abord, c'est une mauvaise idée parce que les conteneurs standard n'ont pas de virtuel destructeurs. Vous ne devez jamais utiliser quelque chose polymorphically qui n'ont pas les destructeurs virtuels, parce que vous ne pouvez pas garantir de nettoyage dans votre classe dérivée.

Règles de base pour virtuel dtors

Deuxièmement, il est vraiment une mauvaise conception. Et il y a en fait plusieurs raisons, il est un mauvais design. Tout d'abord, vous devriez toujours étendre les fonctionnalités de conteneurs standard grâce à des algorithmes qui fonctionnent de façon générique. C'est simple, la complexité de la raison - si vous devez écrire un algorithme pour chaque conteneur, il s'applique et vous avez M conteneurs et N algorithmes, c'est à dire M x N méthodes que vous devez écrire. Si vous écrivez votre algorithmes de façon générique, vous avez N algorithmes seulement. Ainsi, vous obtenez beaucoup plus de les réutiliser.

Il est aussi vraiment mauvaise conception parce que vous êtes briser une bonne encapsulation en héritant du conteneur. Une bonne règle de base est: si vous pouvez accomplir ce que vous avez besoin de l'aide de l'interface publique d'un type, faire de ce nouveau comportement externe du type. Cela améliore l'encapsulation. Si c'est un nouveau comportement que vous souhaitez mettre en œuvre, d'en faire un espace de noms de la portée de la fonction (comme les algorithmes). Si vous avez un nouvel invariant d'imposer, à l'utilisation de confinement dans une classe.

Un classique de la description de l'encapsulation

Enfin, en général, vous ne devriez jamais penser à propos de l'héritage comme un moyen d'étendre le comportement d'une classe. C'est l'un des grand méchant mensonges de début de la programmation orientée objet de la théorie qui est lié à un manque de réflexion sur la réutilisation, et elle continue à être enseignée et promu à ce jour, même si il est clair que la théorie de pourquoi c'est mauvais. Lorsque vous utiliser l'héritage pour étendre comportement, vous êtes liant cette étendue comportement de l'interface du contrat d'une manière qui lie les utilisateurs mains à de futurs changements. Par exemple, disons que vous avez une classe de type Socket qui communique à l'aide du protocole TCP et vous étendre son comportement par la dérivation d'une classe SSLSocket de Prise et de mise en œuvre le comportement du supérieur SSL pile de protocole sur le dessus de la Prise. Maintenant, disons que vous obtenez une nouvelle obligation d'avoir le même protocole de communication, mais sur une ligne d'USB, ou sur la téléphonie. Vous auriez besoin de couper et coller tout ce qui travailler à une nouvelle classe qui dérive d'une classe d'USB, ou un service de la Téléphonie classe. Et maintenant, si vous trouvez un bug, vous devez le résoudre dans les trois lieux, ce qui n'est pas toujours le cas, ce qui signifie que les bogues de prendre plus de temps et pas toujours fixe...

C'est le général de toute hiérarchie d'héritage A->B->C->... Lorsque vous souhaitez utiliser les comportements que vous avez étendu dans les classes dérivées, comme B, C, .. sur les objets qui ne sont pas de la classe de base, vous avez la refonte ou vous êtes à la duplication de mise en œuvre. Cela conduit à de très monolithique des dessins qui sont très difficiles à modifier en bas de la route (pensez à Microsoft MFC, ou leur .NET, ou bien, ils font cette erreur d'un lot). Au lieu de cela, vous devriez toujours penser à l'extension au travers de la composition à chaque fois que possible. L'héritage doit être utilisé lorsque vous pensez "Ouvert / Fermé Principe". Vous devriez avoir les classes de base abstraites et dynamique polymorphisme d'exécution à travers hérité de la classe, chacun sera pleinement mise en œuvre. Les hiérarchies ne devrait pas l'être profond presque toujours à deux niveaux. Seule l'utilisation de plus de deux lorsque vous avez différents dynamique de catégories que d'aller à une variété de fonctions qui ont besoin de cette distinction pour le type de sécurité. Dans ces cas, l'utilisation résumé des bases jusqu'à ce que la feuille de classes, dont la mise en œuvre.

41voto

Emilio Garavaglia Points 9189

Peut-être beaucoup de gens ici ne sera pas comme cette réponse, mais il est temps pour certains de l'hérésie pour être dit et oui ... être dit aussi que "le roi est nu!"

Toute la motivation à l'encontre de la dérivation sont faibles. La dérivation n'est pas différent que de la composition. C'est juste un moyen de "mettre les choses ensemble". La Composition met des choses ensemble, leur donnant des noms, l'héritage n'sans donner des noms explicites.

Si vous avez besoin d'un vecteur qui a la même interface et la mise en œuvre de std::vect plus quelque chose de plus, vous pouvez:

utilisation de la composition et de réécrire tout l'objet incorporé prototypes de fonction la mise en œuvre de la fonction que les délégués (et si ils sont 10000... oui: être prêt à réécrire tous ceux 10000) ou...

en hériter et ajouter juste ce dont vous avez besoin (et ... juste réécrire les constructeurs, jusqu'à ce que le C++, les avocats de décider de les laisser être héritables ainsi: je me souviens encore de 10 an zélote de la discussion sur "pourquoi ctors ne peut pas s'appeler les uns les autres" et pourquoi c'est un "bad bad thing" ... jusqu'à ce que C++11 est permis et tout à coup tous ces fanatiques de la fermer!) et de laisser les nouvelles destructeur non virtuel que c'était l'original.

Comme pour chaque classe qui a certains de méthode virtuelle et à quelques pas, vous savez que vous ne pouvez pas prétendre à invoquer le non virtuel méthode de la dérivée, en répondant à la base, la même chose s'applique pour les supprimer. Il n'y a pas de raison, juste pour le supprimer pour faire semblant particulière de soins. Un programmeur qui sait que ce n'est pas du virtuel n'est pas appelable abordant la base sait aussi ne pas utiliser le supprimer de votre base, après l'affectation des dérivés.

Tous les "éviter" "ne pas faire" toujours le son comme une "moralisation" de quelque chose qui est nativement agnostique. Toutes les caractéristiques d'une langue existent pour résoudre un problème. Le fait un moyen de résoudre le problème est bonne ou mauvaise dépend du contexte, et non pas sur la fonction elle-même. Si ce que vous faites doit servir un grand nombre de conteneurs, l'héritage est probablement pas la façon dont (vous avez à refaire pour tous). Si c'est pour un cas particulier ... l'héritage est une façon de composer. Oubliez la programmation orientée objet purisms: C++ n'est pas un "pur POO" et conteneurs ne sont pas de la POO à tous.

10voto

Armen Tsirunyan Points 59548

Vous devriez vous abstenir de tirer publiquement à partir de la norme contianers. Vous pouvez choisir entre privé d'héritage et de composition , et il me semble que toutes les directives générales indiquent que la composition est mieux ici, puisque vous n'avez pas de remplacer n'importe quelle fonction. Ne pas tirer publiquement forme des conteneurs STL - il n'y a vraiment pas besoin de ça.

Par ailleurs, si vous souhaitez ajouter un tas d'algorithmes pour le conteneur, envisager de les ajouter autonome des fonctions prenant un itérateur gamme.

5voto

Bo Persson Points 42821

Le problème, c'est que vous, ou quelqu'un d'autre, pourrait accidentellement passer votre classe étendue à une fonction attend une référence à la classe de base. Efficace (et en silence!) couper les extensions et de créer une certaine dur de trouver des bugs.

Avoir à écrire certaines des fonctions de transfert semble comme un petit prix à payer en comparaison.

2voto

Puppy Points 90818

Parce que vous ne peut jamais garantir que vous n'avez pas utilisés d'une façon polymorphe. Vous êtes à la mendicité pour les problèmes. En prenant l'effort d'écrire quelques fonctions n'est pas une grosse affaire, et, ainsi, même l'envie de faire ce qui est douteux, au mieux. Ce qui s'est passé à l'encapsulation?

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