les types basés sur des modèles sont supposés suivre un "concept" (Input Iterator, Forward Iterator, etc ...) où les détails réels du concept sont entièrement définis par la mise en œuvre de la fonction / classe modèle, et non par la classe du type utilisé avec le modèle, qui est un peu anti-utilisation de la POO.
Je pense que vous comprenez mal l'utilisation prévue des concepts par les modèles. L'itérateur avancé, par exemple, est un concept très bien défini. Pour trouver les expressions qui doivent être valides pour qu'une classe soit un itérateur avancé, et leur sémantique, y compris la complexité de calcul, consultez la norme ou http://www.sgi.com/tech/stl/ForwardIterator.html (vous devez suivre les liens vers Input, Output et Trivial Iterator pour tout voir).
Ce document est une interface parfaitement bonne, et "les détails réels du concept" sont définis juste là. Ils ne sont pas définis par les implémentations des itérateurs avancés, et ils ne sont pas non plus définis par les algorithmes qui utilisent les itérateurs avancés.
Les différences dans la gestion des interfaces entre STL et Java sont de trois ordres:
1) STL définit des expressions valides en utilisant l'objet, tandis que Java définit des méthodes qui doivent être appelables sur l'objet. Bien sûr, une expression valide peut être un appel de méthode (fonction membre), mais ce n'est pas obligatoire.
2) Les interfaces Java sont des objets d'exécution, tandis que les concepts STL ne sont pas visibles à l'exécution même avec RTTI.
3) Si vous ne parvenez pas à valider les expressions valides requises pour un concept STL, vous obtenez une erreur de compilation non spécifiée lorsque vous instanciez un modèle avec le type. Si vous ne parvenez pas à implémenter une méthode requise d'une interface Java, vous obtenez une erreur de compilation spécifique le disant.
Cette troisième partie est si vous aimez une sorte de "typage du canard" (à la compilation): les interfaces peuvent être implicites. En Java, les interfaces sont quelque peu explicites: une classe "est" Iterable si et seulement si elle dit qu'elle implémente Iterable. Le compilateur peut vérifier que les signatures de ses méthodes sont toutes présentes et correctes, mais la sémantique est toujours implicite (c'est-à-dire qu'elles sont documentées ou non, mais seulement plus de code (tests unitaires) peut vous dire si l'implémentation est correcte).
En C ++, comme en Python, la sémantique et la syntaxe sont implicites, bien qu'en C ++ (et en Python si vous obtenez le préprocesseur de typage fort), vous obtenez de l'aide du compilateur. Si un programmeur requiert une déclaration explicite d'interfaces de type Java par la classe d'implémentation, l'approche standard consiste à utiliser des traits de type (et l'héritage multiple peut empêcher que cela soit trop verbeux). Ce qui manque, par rapport à Java, c'est un modèle unique que je peux instancier avec mon type, et qui se compilera si et seulement si toutes les expressions requises sont valides pour mon type. Cela me dirait si j'ai implémenté tous les bits requis, "avant de l'utiliser". C'est une commodité, mais ce n'est pas le cœur de la POO (et il ne teste toujours pas la sémantique,
STL peut ou non être suffisamment OO à votre goût, mais il sépare certainement l'interface proprement de l'implémentation. Il n'a pas la capacité de Java à réfléchir sur les interfaces, et il signale les violations des exigences d'interface différemment.
vous pouvez dire que la fonction ... attend un itérateur avancé uniquement en regardant sa définition, où vous auriez besoin de regarder l'implémentation ou la documentation pour ...
Personnellement, je pense que les types implicites sont une force lorsqu'ils sont utilisés de manière appropriée. L'algorithme dit ce qu'il fait avec ses paramètres de modèle, et l'implémenteur s'assure que ces choses fonctionnent: c'est exactement le dénominateur commun de ce que les "interfaces" devraient faire. De plus, avec STL, il est peu probable que vous utilisiez, par exemple, la std::copy
recherche de sa déclaration directe dans un fichier d'en-tête. Les programmeurs devraient déterminer ce qu'une fonction prend en fonction de sa documentation, et pas seulement de la signature de la fonction. Cela est vrai en C ++, Python ou Java. Il y a des limites à ce qui peut être réalisé avec la frappe dans n'importe quelle langue, et essayer d'utiliser la frappe pour faire quelque chose qu'il ne fait pas (vérifier la sémantique) serait une erreur.
Cela dit, les algorithmes STL nomment généralement leurs paramètres de modèle d'une manière qui indique clairement le concept requis. Cependant, il s'agit de fournir des informations supplémentaires utiles dans la première ligne de la documentation, et non de rendre les déclarations avancées plus informatives. Il y a plus de choses que vous devez savoir que ce qui peut être encapsulé dans les types de paramètres, vous devez donc lire les documents. (Par exemple, dans les algorithmes qui prennent une plage d'entrée et un itérateur de sortie, il est probable que l'itérateur de sortie ait besoin de suffisamment "d'espace" pour un certain nombre de sorties en fonction de la taille de la plage d'entrée et peut-être des valeurs qui s'y trouvent. Essayez de taper fortement cela. )
Voici Bjarne sur les interfaces explicitement déclarées: http://www.artima.com/cppsource/cpp0xP.html
En générique, un argument doit être d'une classe dérivée d'une interface (l'équivalent C ++ de l'interface est une classe abstraite) spécifiée dans la définition du générique. Cela signifie que tous les types d'arguments génériques doivent tenir dans une hiérarchie. Cela impose des contraintes inutiles aux conceptions et nécessite une prévoyance déraisonnable de la part des développeurs. Par exemple, si vous écrivez un générique et que je définis une classe, les gens ne peuvent pas utiliser ma classe comme argument de votre générique à moins que je ne connaisse l'interface que vous avez spécifiée et que j'en ai dérivé ma classe. C'est rigide.
En regardant l'inverse, avec la frappe de canard, vous pouvez implémenter une interface sans savoir que l'interface existe. Ou quelqu'un peut écrire une interface délibérément de telle sorte que votre classe l'implémente, après avoir consulté vos documents pour voir qu'ils ne demandent rien de ce que vous ne faites pas déjà. C'est flexible.