8
votes

Pourquoi n'y a-t-il pas une base commune pour les conteneurs de la bibliothèque standard?

juste hors d'intérêt ...

Si je devais concevoir une bibliothèque de conteneurs, je les dérangerais sûrement d'une classe de base commune, qui aurait (peut-être abstrait) des déclarations de méthodes telles que taille () et insert () .

Y a-t-il une forte raison pour que les conteneurs de bibliothèque standard ne soient pas mis en œuvre comme ça?


1 commentaires

C'est une abstraction inutile. Nous avons des itérateurs pour itération et de nombreuses propriétés de conteneurs spécifiques ne peuvent être correctement abstraites de manière utile.


5 Réponses :


1
votes

Il n'y a pas de bonne raison d'introduire une telle classe de base. Qui en bénéficierait? Où serait-il utile?

algorithmes nécessitant une interface «générique» utilise des itérateurs. Il n'y a pas de moyen courant d'insertion d'éléments à différents conteneurs. En fait, vous ne pouvez même pas insérer des éléments à certains conteneurs.


1 commentaires

N'oubliez pas l'insert () S qui peut être des modèles.



13
votes

en C ++, l'héritage est utilisé pour la polymorphie d'exécution (lecture: réutilisation de l'interface d'exécution). Il vient avec les frais généraux d'une redirection via la table à l'autre.

Pour avoir la même interface pour plusieurs classes de conteneurs (afin que l'API soit prévisible et que des algorithmes puissent faire des hypothèses), il n'y a pas besoin d'héritage. Donnez-leur simplement les mêmes méthodes et vous avez terminé. Les conteneurs (et les algorithmes) en C ++ sont mis en œuvre sous forme de modèles, ce qui signifie que vous obtenez la polymorphie de la compilation.

Cela évite les frais généraux d'exécution, et il est aligné avec le mantra C ++ "uniquement payer pour ce dont vous avez besoin".


3 commentaires

Vous avez seulement des vtables lorsque vous avez des méthodes virtuelles. Vous ne payez rien lorsque vous utilisez des méthodes à partir d'une classe de base.


@Scharron: C'est correct. Cependant, lorsque vous avez une classe de base, il est possible que les clients d'utiliser un pointeur de classe de base sur une classe dérivée. Calling Supprimer sur ce pointeur doit certainement également exécuter le destructeur de la classe dérivée - ce qui signifie que vous avez besoin d'un destructeur virtuel. Et puis vous avez un circuit bancaire.


Totalement à droite. Le point d'utiliser la classe de base consistait à "factoriser" des choses, de sorte que l'objet de base pourrait être masqué et supprimer le comportement sur le pointeur de base aurait pu être indéfini. Mais c'est laid ;-)



1
votes

Il serait logique que vous puissiez écrire du code indépendant du conteneur que vous utilisez. Mais ce n'est pas le cas. Je vous recommande de lire ce chapitre "Item 2: méfiez-vous l'illusion du code indépendant du conteneur" du livre efficace STL .


1 commentaires

Pour ce que ça vaut la peine, vous peut écrire du code indépendant du conteneur - même sans aucune héritage du tout. Donnez simplement à chaque conteneur une méthode avec le même nom (dire, "vide") et, espérons-le, le même sémantique et que vous avez terminé.



1
votes

Dans un autre point de vue: En ce moment, si vous les utilisez de manière générique, le compilateur a sur toutes les informations qu'il peut avoir, permettant ainsi une optimisation approfondie. Merci aux implémentations de modèles.

Maintenant, si, par exemple, la liste et le vecteur implémenteraient une interface OO abstraite comme Push_backable, et vous utiliseriez un pointeur abstrait (push_backable *) -> push_back (...) dans votre code, le compilateur perdrait beaucoup d'informations et donc des opportunités d'optimisation.

Ce sont des opérations typiques pouvant apparaître dans des boucles intérieures et vous voulez vraiment l'optimisation maximale possible pour eux. Voir aussi la réponse de Frich Raabe.


0 commentaires

4
votes

Les collections Java (que vous avez probablement à l'esprit) ont un tas de méthodes communes implémentées dans abstractcollection , qui reposent sur le dessus de la taille () et et Itérateur () Méthodes implémentées dans la classe de béton. Ensuite, IIRC il y a plus de telles méthodes dans abstracttractlist et ainsi de suite. Certaines sous-classes remplacent la plupart de ces méthodes, quelques-unes peuvent s'éloigner de la mise en œuvre peu plus que les méthodes abstraites requises. Certaines des méthodes sont véritablement implémentées en commun dans la plupart ou toutes les collections partageant une interface. Par exemple, celui qui vous donne une vue non modifiable est un pita total à écrire, dans mon expérience amère.

Les conteneurs C ++ ont quelques fonctions membres en commun à tous les conteneurs, dont aucun ne pouvait être mis en œuvre de la même manière pour tous les conteneurs [*]. Lorsqu'il existe des algorithmes communs pouvant être effectués à l'aide d'un itérateur (comme Rechercher ), ce sont des fonctions libres dans , loin de ce que l'héritage obtient un look-in .

Il existe également des fonctions membres communes à toutes les séquences, à tous les tableaux associatifs, etc., mais encore une fois pour chacun de ces concepts, il n'y a pas beaucoup de points communs entre les implémentations de différentes structures de données concrètes.

Si, finalement, se résume à une question de philosophie de conception. Java et C ++ ont à la fois une réutilisation de code concernant des conteneurs. En C ++, cette réutilisation du code passe par des modèles de fonction dans . En Java, une partie de celle-ci provient de l'héritage.

La principale différence pratique est probablement que, dans Java, votre fonction peut accepter un java.util.collection sans savoir quel type de collection est. En C ++, un modèle de fonction peut accepter tout conteneur, mais une fonction non-modèle ne peut pas. Cela affecte le style de codage des utilisateurs et est également informé par IT - Les programmeurs Java sont plus susceptibles d'atteindre le polymorphisme d'exécution que les programmeurs C ++.

du point de vue d'un implauteur, si vous écrivez la bibliothèque de conteneurs C ++, rien ne vous empêche de partager des classes de base privées entre différents conteneurs dans std , si vous sentez que cela aide à réutiliser le code.

[*] maintenant que taille () est garanti o (1) in c ++ 11, vide () pourrait probablement être mis en œuvre en commun. En C ++ 03 Taille () Simely "devrait" être O (1) , et il y avait des implémentations de std :: Liste qui a pris avantage de mettre en oeuvre l'une des versions de Splice dans O (1) au lieu du O (n) il faudrait mettre à jour le taille.


0 commentaires