Pourquoi la STL C ++ est-elle si fortement basée sur des modèles? (et pas sur * interfaces *)

Je veux dire, en dehors de son nom obligatoire (la bibliothèque de modèles standard) …

C ++ avait initialement pour but de présenter les concepts de la POO dans C. C’est-à-dire que vous pouviez déterminer ce qu’une entité spécifique pouvait et ne pouvait pas faire (indépendamment de la manière dont elle le faisait) en fonction de sa hiérarchie de classes et de classes. Certaines compositions de capacités sont plus difficiles à décrire de cette manière en raison de la problématique de l’inheritance multiple, et du fait que C ++ supporte le concept d’interfaces de manière quelque peu maladroite (comparée à java, etc.), mais elle existe (et pourrait être amélioré).

Et puis les modèles sont entrés en jeu, avec le STL. Le TSL a semblé prendre les concepts classiques de la POO et les vider de leur contenu, en utilisant plutôt des gabarits.

Il devrait y avoir une distinction entre les cas où des modèles sont utilisés pour généraliser des types où les types de thèmes ne sont pas pertinents pour le fonctionnement du modèle (conteneurs, par exemple). Avoir un vector est parfaitement logique.

Cependant, dans de nombreux autres cas (iterators et algorithmes), les types de modèles sont supposés suivre un “concept” (Iterator, Forward Iterator, etc.) où les détails réels du concept sont entièrement définis par l’implémentation du template. fonction / classe, et non pas par la classe du type utilisé avec le modèle, ce qui est un peu anti-utilisation de la POO.

Par exemple, vous pouvez indiquer la fonction:

 void MyFunc(ForwardIterator *I); 

Mise à jour: Comme la question initiale n’était pas claire, ForwardIterator peut être modélisé lui-même pour autoriser tout type ForwardIterator. Le contraire est d’avoir ForwardIterator comme concept.

attend un Itérateur en avant uniquement en examinant sa définition, où vous devrez soit examiner l’implémentation ou la documentation pour:

 template  void MyFunc(Type *I); 

Deux affirmations que je peux faire en faveur de l’utilisation de templates: le code compilé peut être rendu plus efficace en compilant le template pour chaque type utilisé, au lieu d’utiliser des vtables. Et le fait que les modèles peuvent être utilisés avec des types natifs.

Cependant, je cherche une raison plus profonde pour laquelle l’abandon de la POO classique en faveur de la modélisation pour la STL? (En supposant que vous lisiez aussi loin: P)

La réponse courte est “parce que C ++ a évolué”. Oui, à la fin des années 70, Stroustrup avait l’intention de créer un C mis à niveau avec des fonctionnalités OOP, mais il y a longtemps. Au moment où la langue a été normalisée en 1998, ce n’était plus une langue de POO. C’était un langage multi-paradigme. Il avait certainement un certain support pour le code de la POO, mais il disposait également d’un langage de gabarit complet, il permettait la métaprogrammation au moment de la compilation, et les utilisateurs avaient découvert la programmation générique. Soudain, la POO ne semblait pas si importante. Pas quand nous pouvons écrire du code plus simple, plus concis et plus efficace en utilisant des techniques disponibles via des modèles et une programmation générique.

La POO n’est pas le Saint Graal. C’est une idée mignonne et c’était une amélioration par rapport aux langages procéduraux dans les années 70 quand il a été inventé. Mais honnêtement, tout n’est pas résolu. Dans de nombreux cas, il est maladroit et verbeux et ne favorise pas vraiment le code réutilisable ou la modularité.

C’est pourquoi la communauté C ++ est aujourd’hui beaucoup plus intéressée par la programmation générique et pourquoi tout le monde commence enfin à se rendre compte que la functional programming est également très intelligente. OOP à elle seule n’est tout simplement pas un beau spectacle.

Essayez de dessiner un graphe de dépendance d’un STL hypothétique “OOP-ified”. Combien de classes devraient connaître les uns des autres? Il y aurait beaucoup de dépendances. Seriez-vous en mesure d’inclure uniquement l’en vector tête vector , sans obtenir d’ iterator ou même iostream ? Le STL rend cela facile. Un vecteur connaît le type d’iterator qu’il définit, et c’est tout. Les algorithmes STL ne savent rien . Ils n’ont même pas besoin d’inclure un en-tête d’iterator, même s’ils acceptent tous des iterators en tant que parameters. Quel est le plus modulaire alors?

La STL peut ne pas suivre les règles de la POO comme Java le définit, mais n’at-elle pas les objectives de la POO? Ne parvient-il pas à la réutilisation, au faible couplage, à la modularité et à l’encapsulation?

Et ne parvient-il pas à atteindre ces objectives mieux qu’une version OOP-ified?

En ce qui concerne la raison pour laquelle le TSL a été adopté dans la langue, plusieurs choses ont abouti au TSL.

Tout d’abord, les modèles ont été ajoutés à C ++. Ils ont été ajoutés pour la même raison que les génériques ont été ajoutés à .NET. Cela semblait une bonne idée de pouvoir écrire des choses comme des «conteneurs de type T» sans jeter le type de sécurité. Bien sûr, la mise en œuvre qu’ils ont mise en place était beaucoup plus complexe et puissante.

Ensuite, les gens ont découvert que le mécanisme de gabarit qu’ils avaient ajouté était encore plus puissant que prévu. Et quelqu’un a commencé à utiliser des modèles pour écrire une bibliothèque plus générique. L’un inspiré par la functional programming, l’autre utilisant toutes les nouvelles fonctionnalités de C ++.

Il l’a présenté au comité du langage C ++, qui a mis du temps à s’y habituer car il semblait si étrange et différent, mais a finalement réalisé que cela fonctionnait mieux que les équivalents traditionnels de la POO qu’ils auraient dû inclure autrement . Ils y ont donc apporté quelques ajustements et l’ont adopté dans la bibliothèque standard.

Ce n’était pas un choix idéologique, ce n’était pas un choix politique de «voulons-nous être OPO ou non», mais très pragmatique. Ils ont évalué la bibliothèque et ont constaté que cela fonctionnait très bien.

Dans tous les cas, les deux raisons que vous mentionnez pour favoriser le TSL sont absolument essentielles.

La bibliothèque standard C ++ doit être efficace. S’il est moins efficace que, par exemple, le code C roulé à la main équivalent, alors les gens ne l’utiliseront pas. Cela réduirait la productivité, augmenterait les risques de bogues et, globalement, ce serait une mauvaise idée.

Et la STL doit fonctionner avec les types primitifs, car les types primitifs sont tout ce que vous avez en C, et ils constituent une partie majeure des deux langages. Si la STL ne fonctionnait pas avec des tableaux natifs, cela serait inutile .

Votre question suppose fortement que la POO est «la meilleure». Je suis curieux d’entendre pourquoi. Vous demandez pourquoi ils ont “abandonné la POO classique”. Je me demande pourquoi ils auraient dû s’en tenir à cela. Quels avantages aurait-il eu?

La réponse la plus directe à ce que je pense vous demander / vous plaindre est la suivante: L’hypothèse selon laquelle C ++ est un langage OOP est une fausse hypothèse.

C ++ est un langage multi-paradigme. Il peut être programmé selon les principes de la POO, il peut être programmé de manière procédurale, il peut être programmé de manière générique (modèles), et avec C ++ 11 (anciennement C ++ 0x), certaines choses peuvent même être programmées fonctionnellement.

Les concepteurs de C ++ considèrent cela comme un avantage, de sorte qu’ils soutiendraient que contraindre C ++ à agir comme un langage purement POO lorsque la programmation générique résout le problème mieux et, de manière plus générique , serait un pas en arrière.

D’après ce que je comprends, Stroustrup préférait à l’origine une conception de conteneur de type “OOP” et, en fait, ne voyait pas d’autre moyen de le faire. Alexander Stepanov est le responsable du STL et ses objectives n’incluaient pas “le rendre orienté object” :

C’est le point fondamental: les algorithmes sont définis sur des structures algébriques. Il m’a fallu deux ans pour comprendre que vous devez étendre la notion de structure en ajoutant des exigences de complexité aux axiomes réguliers. … Je crois que les théories des iterators sont aussi essentielles à l’informatique que les théories des anneaux ou des espaces de Banach sont au cœur des mathématiques. Chaque fois que je regarderais un algorithme, j’essaierais de trouver une structure sur laquelle il est défini. Donc, ce que je voulais faire était de décrire les algorithmes de manière générique. C’est ce que j’aime faire. Je peux passer un mois à travailler sur un algorithme bien connu essayant de trouver sa représentation générique. …

STL, du moins pour moi, représente la seule façon dont la programmation est possible. Il est en effet très différent de la programmation en C ++ telle qu’elle a été présentée et est toujours présentée dans la plupart des manuels. Mais, vous voyez, je n’essayais pas de programmer en C ++, j’essayais de trouver la bonne façon de gérer les logiciels. …

J’ai eu beaucoup de faux départs. Par exemple, j’ai passé des années à essayer de trouver un usage de l’inheritance et des objects virtuels, avant de comprendre pourquoi ce mécanisme était fondamentalement défectueux et ne devrait pas être utilisé. Je suis très heureux que personne ne puisse voir toutes les étapes intermédiaires – la plupart d’entre eux étaient très stupides.

(Il explique pourquoi l’inheritance et les objects virtuels – alias design orienté object “étaient fondamentalement imparfaits et ne devraient pas être utilisés” dans le rest de l’interview).

Une fois que Stepanov a présenté sa bibliothèque à Stroustrup, Stroustrup et d’autres ont fait des efforts herculéens pour le faire entrer dans la norme ISO C ++ (même interview):

Le soutien de Bjarne Stroustrup était crucial. Bjarne voulait vraiment STL dans le standard et si Bjarne veut quelque chose, il l’obtient. … Il m’a même forcé à faire des changements dans le STL que je ne ferais jamais pour quelqu’un d’autre … c’est la personne la plus résolue que je connaisse. Il fait avancer les choses. Il lui a fallu du temps pour comprendre ce qu’était le STL, mais quand il l’a fait, il était prêt à le faire passer. Il a également consortingbué à STL en défendant l’idée selon laquelle plus d’une méthode de programmation était valide – contre la panique et le battage médiatique depuis plus d’une décennie, et recherchant une combinaison de flexibilité, d’efficacité, de surcharge et de sécurité des modèles qui ont rendu STL possible. Je voudrais dire très clairement que Bjarne est le concepteur linguistique par excellence de ma génération.

La réponse se trouve dans cette interview avec Stepanov, l’auteur du TSL:

Oui. STL n’est pas orienté object. Je pense que le caractère orienté object est presque aussi un canular que l’intelligence artificielle. Je n’ai pas encore vu un morceau de code intéressant provenant de ces personnes.

Pourquoi une conception OOP pure vers une bibliothèque de structures de données et d’algorithmes serait-elle préférable?! La POO n’est pas la solution pour tout.

IMHO, STL est la bibliothèque la plus élégante que j’ai jamais vue 🙂

pour votre question,

vous n’avez pas besoin de polymorphism à l’exécution, c’est un avantage pour STL d’implémenter la bibliothèque en utilisant un polymorphism statique, c’est-à-dire une efficacité. Essayez d’écrire un sorting générique ou une distance ou quel que soit l’algorithme qui s’applique à TOUS les conteneurs! Votre sorting en Java appelle les fonctions dynamics par n-niveaux à exécuter!

Vous avez besoin de choses stupides, comme Boxing et Unboxing, pour cacher des suppositions désagréables sur les langages purement OOP.

Le seul problème que je vois avec STL, et les modèles en général, ce sont les messages d’erreur horribles. Ce qui sera résolu en utilisant les concepts en C ++ 0X.

Comparer STL aux collections en Java, c’est comme comparer Taj Mahal à ma maison 🙂

les types modélisés sont supposés suivre un “concept” (Iterator en entrée, Itérateur en avant, etc.) où les détails réels du concept sont entièrement définis par l’implémentation de la fonction / classe du template, et non par la classe du type utilisé avec le modèle, ce qui est un peu anti-usage de la POO.

Je pense que vous comprenez mal l’utilisation prévue des concepts par des modèles. Forward Iterator, par exemple, est un concept très bien défini. Pour trouver les expressions qui doivent être valides pour qu’une classe soit un iterator avant, et leur sémantique, y compris la complexité de calcul, vous regardez 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 adaptée et “les détails réels du concept” sont définis ici. Ils ne sont pas définis par les implémentations des iterators directs, et ils ne sont pas non plus définis par les algorithmes qui utilisent les iterators directs.

Les différences de traitement des interfaces entre STL et Java sont de trois ordres:

1) STL définit des expressions valides utilisant l’object, alors que Java définit des méthodes qui doivent pouvoir être appelées sur l’object. Bien sûr, une expression valide peut être une méthode (fonction membre), mais elle ne doit pas nécessairement l’être.

2) Les interfaces Java sont des objects d’exécution, alors 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 requirejses 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 requirejse d’une interface Java, vous obtenez une erreur de compilation spécifique.

Cette troisième partie est si vous aimez une sorte de “compilation de canards” (à 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 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 que plus de code (tests unitaires) peut vous dire si

En C ++, comme en Python, la sémantique et la syntaxe sont implicites, bien qu’en C ++ (et en Python si vous avez le préprocesseur à fort typage), vous obtenez de l’aide du compilateur. Si un programmeur requirejs une déclaration explicite de type Java des interfaces par la classe d’implémentation, l’approche standard consiste à utiliser des traits de type (et l’inheritance multiple peut empêcher que cela soit trop verbeux). Ce qui manque, comparé à Java, est un modèle unique que je peux instancier avec mon type et qui comstackra si et seulement si toutes les expressions requirejses sont valides pour mon type. Cela me dirait si j’ai implémenté tous les bits requirejs “avant de l’utiliser”. C’est une commodité, mais ce n’est pas le cœur de la POO (et cela ne teste toujours pas la sémantique, et le code pour tester la sémantique testerait naturellement aussi la validité des expressions en question).

STL peut être ou ne pas être suffisamment OO à votre goût, mais il sépare certainement l’interface proprement de l’implémentation. Il manque la capacité de Java à faire de la reflection sur les interfaces, et il signale les violations des exigences de l’interface différemment.

vous pouvez dire à la fonction … attend un iterator avant seulement en regardant sa définition, où vous aurez 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 parameters 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, std::copy en recherchant sa déclaration dans un fichier d’en-tête. Les programmeurs doivent déterminer ce que prend une fonction en fonction de sa documentation, pas seulement sur la signature de la fonction. Cela est vrai en C ++, Python ou Java. Il y a des limites à ce que l’on peut obtenir en tapant dans n’importe quelle langue, et essayer d’utiliser la saisie pour faire quelque chose qu’elle ne fait pas (vérifier la sémantique) serait une erreur.

Cela dit, les algorithmes STL nomment généralement leurs parameters de modèle de manière à préciser le concept requirejs. Cependant, il s’agit de fournir des informations supplémentaires utiles dans la première ligne de la documentation, et non de faire des déclarations plus informatives. Il y a plus de choses à savoir que ce que vous pouvez encapsuler dans les types de parameters, vous devez donc lire les documents. (Par exemple, dans les algorithmes qui prennent une plage d’entrée et un iterator de sortie, il est probable que l’iterator de sortie ait besoin d’un “espace” suffisant pour un certain nombre de sorties en fonction de la taille de la plage d’entrée. )

Voici Bjarne sur les interfaces explicitement déclarées: http://www.artima.com/cppsource/cpp0xP.html

Dans les génériques, un argument doit appartenir à une classe dérivée d’une interface (l’équivalent de C ++ à l’interface est la 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 sur les conceptions, ce qui nécessite une vision 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 de connaître l’interface que vous avez spécifiée et d’y avoir dérivé ma classe. C’est rigide

En regardant le contraire, avec le typage 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 pour que votre classe l’implémente, après avoir consulté vos docs pour voir qu’ils ne demandent rien de ce que vous ne faites pas déjà. C’est flexible.

“La POO signifie pour moi uniquement la messagerie, la rétention locale, la protection et le masquage des processus d’état et la liaison extrêmement tardive de toutes choses. Cela peut être fait dans Smalltalk et dans LISP. D’autres systèmes sont possibles, mais Je ne les connais pas. ” – Alan Kay, créateur de Smalltalk.

C ++, Java et la plupart des autres langages sont assez éloignés de la POO classique. Cela dit, plaider pour des idéologies n’est pas très productif. Le C ++ n’est en aucun cas pur, il implémente donc des fonctionnalités qui semblent avoir un sens pragmatique à l’époque.

Le problème de base avec

 void MyFunc(ForwardIterator *I); 

c’est comment obtenir en toute sécurité le type de chose que l’iterator retourne? Avec les modèles, ceci est fait pour vous au moment de la compilation.

STL a commencé avec l’intention de fournir une grande bibliothèque couvrant l’algorithme le plus couramment utilisé – avec pour objective un comportement et des performances constants. Le modèle est devenu un facteur clé pour rendre cette mise en œuvre et cet objective réalisables.

Juste pour fournir une autre référence:

Al Stevens interviewé Alex Stepanov, en mars 1995 de DDJ:

Stepanov a expliqué son expérience de travail et son choix pour une grande bibliothèque d’algorithmes, qui ont fini par évoluer en STL.

Parlez-nous de votre intérêt à long terme pour la programmation générique

….. On m’a ensuite proposé un travail chez Bell Laboratories dans le groupe C ++ sur les bibliothèques C ++. Ils m’ont demandé si je pouvais le faire en C ++. Bien sûr, je ne connaissais pas le C ++ et, bien sûr, je l’ai dit. Mais je ne pouvais pas le faire en C ++, car en 1987, C ++ ne disposait pas de modèles indispensables pour activer ce style de programmation. L’inheritance était le seul mécanisme pour obtenir la généricité et ce n’était pas suffisant.

Même maintenant, l’inheritance C ++ n’est pas d’une grande utilité pour la programmation générique. Discutons pourquoi. De nombreuses personnes ont tenté d’utiliser l’inheritance pour implémenter des structures de données et des classes de conteneur. Comme nous le soaps maintenant, il y a eu peu ou pas de tentatives réussies. L’inheritance C ++ et le style de programmation qui lui est associé sont considérablement limités. Il est impossible d’implémenter une conception qui inclut aussi sortingvialement que l’égalité en l’utilisant. Si vous commencez avec une classe de base X à la racine de votre hiérarchie et définissez un opérateur d’égalité virtuelle sur cette classe qui prend un argument de type X, dérivez la classe Y de la classe X. Quelle est l’interface de l’égalité? Il a une égalité qui compare Y à X. En utilisant les animaux comme exemple (les personnes OO aiment les animaux), définissent les mammifères et dérivent les girafes des mammifères. Ensuite, définissez un compagnon de fonction membre, où l’animal s’accouple avec l’animal et renvoie un animal. Ensuite, vous dérivez des girafes d’animaux et, bien sûr, il y a un compagnon de fonction où la girafe s’accouple avec l’animal et retourne un animal. Ce n’est certainement pas ce que vous voulez. Bien que le couplage ne soit pas très important pour les programmeurs C ++, l’égalité est. Je ne connais pas un seul algorithme où l’égalité de quelque sorte n’est pas utilisée.

Pour un instant, considérons la bibliothèque standard comme une firebase database de collections et d’algorithmes.

Si vous avez étudié l’histoire des bases de données, vous savez sans doute qu’au début, les bases de données étaient principalement “hiérarchiques”. Les bases de données hiérarchiques correspondent très étroitement à la POO classique, en particulier la variété à un seul inheritance, telle qu’elle est utilisée par Smalltalk.

Au fil du temps, il est apparu que des bases de données hiérarchiques pouvaient être utilisées pour modéliser presque tout, mais dans certains cas, le modèle à inheritance unique était assez ressortingctif. Si vous aviez une porte en bois, il était pratique de pouvoir la regarder soit comme une porte, soit comme un morceau de matière première (acier, bois, etc.).

Ils ont donc inventé des bases de données de modèles de réseau. Les bases de données de modèles de réseau correspondent très étroitement à l’inheritance multiple. C ++ prend complètement en charge l’inheritance multiple, tandis que Java prend en charge une forme limitée (vous pouvez hériter d’une seule classe, mais vous pouvez également implémenter autant d’interfaces que vous le souhaitez).

Les bases de données de modèles hiérarchiques et de modèles de réseaux ont toutes deux disparu de l’utilisation à des fins générales (bien que quelques-unes restnt dans des niches assez spécifiques). Dans la plupart des cas, ils ont été remplacés par des bases de données relationnelles.

Une grande partie de la raison pour laquelle les bases de données relationnelles ont pris le relais a été la polyvalence. Le modèle relationnel est fonctionnellement un sur-ensemble du modèle de réseau (qui est à son tour un sur-ensemble du modèle hiérarchique).

C ++ a largement suivi le même chemin. La correspondance entre l’inheritance simple et le modèle hiérarchique et entre l’inheritance multiple et le modèle de réseau est assez évidente. La correspondance entre les modèles C ++ et le modèle hiérarchique est peut-être moins évidente, mais de toute façon, elle convient parfaitement.

Je n’en ai pas vu de preuve formelle, mais je pense que les capacités des templates sont un sur-ensemble de celles fournies par l’inheritance multiple (qui est clairement un sur-ensemble de l’inerhitance simple). La difficulté réside dans le fait que les modèles sont généralement liés de manière statique – c’est-à-dire que toute la liaison se produit au moment de la compilation, et non de l’exécution. En tant que telle, une preuve formelle que l’inheritance fournit un sur-ensemble des capacités d’inheritance peut être quelque peu difficile et complexe (voire impossible).

Dans tous les cas, je pense que la raison principale est que C ++ n’utilise pas l’inheritance pour ses conteneurs – il n’y a aucune raison de le faire, car l’inheritance ne fournit qu’un sous-ensemble des fonctionnalités fournies par les modèles. Puisque les modèles sont essentiellement une nécessité dans certains cas, ils pourraient aussi bien être utilisés presque partout.

Comment faites-vous des comparaisons avec les ForwardIterator *? C’est-à-dire, comment vérifiez-vous si l’article que vous avez est ce que vous cherchez ou si vous l’avez réussi?

La plupart du temps, j’utiliserais quelque chose comme ceci:

 void MyFunc(ForwardIterator& i) 

ce qui signifie que je sais que je pointe vers MyType, et je sais comment les comparer. Bien qu’il ressemble à un modèle, ce n’est pas vraiment (pas de mot clé “modèle”).

Cette question a beaucoup de bonnes réponses. Il convient également de mentionner que les modèles prennent en charge une conception ouverte. Avec l’état actuel des langages de programmation orientés object, il faut utiliser le modèle de visiteur pour traiter de tels problèmes, et la véritable POO doit prendre en charge la liaison dynamic multiple. Voir Open Multi-Methods for C ++, P. Pirkelbauer, et.al. pour une lecture très intéressante.

Un autre point intéressant des modèles est qu’ils peuvent également être utilisés pour le polymorphism d’exécution. Par exemple

 template Value euler_fwd(size_t N,double t_0,double t_end,Value y_0,const T& func) { auto dt=(t_end-t_0)/N; for(size_t k=0;k 

Notez que cette fonction fonctionnera également si Value est un vecteur quelconque ( pas std :: vector, qui devrait être appelé std::dynamic_array pour éviter toute confusion)

Si func est petit, cette fonction gagnera beaucoup en incrustation. Exemple d'utilisation

 auto result=euler_fwd(10000,0.0,1.0,1.0,[](double x,double y) {return y;}); 

Dans ce cas, vous devez connaître la réponse exacte (2.718 ...), mais il est facile de construire une ODE simple sans solution élémentaire (Astuce: utilisez un polynôme en y).

Maintenant, vous avez une grande expression dans func , et vous utilisez le solveur ODE à de nombreux endroits, de sorte que votre exécutable est pollué par des instanciations de modèles partout. Que faire? La première chose à noter est qu'un pointeur de fonction normal fonctionne. Ensuite, vous voulez append le curry afin d'écrire une interface et une instanciation explicite

 class OdeFunction { public: virtual double operator()(double t,double y) const=0; }; template double euler_fwd(size_t N,double t_0,double t_end,double y_0,const OdeFunction& func); 

Mais l'instanciation ci-dessus ne fonctionne que pour le double , pourquoi ne pas écrire l'interface en tant que modèle:

 template class OdeFunction { public: virtual Value operator()(double t,const Value& y) const=0; }; 

et se spécialiser pour certains types de valeurs communes:

 template double euler_fwd(size_t N,double t_0,double t_end,double y_0,const OdeFunction& func); template vec4_t euler_fwd(size_t N,double t_0,double t_end,vec4_t y_0,const OdeFunction< vec4_t >& func); // (Native AVX vector with four components) template vec8_t euler_fwd(size_t N,double t_0,double t_end,vec8_t y_0,const OdeFunction< vec8_t >& func); // (Native AVX vector with 8 components) template Vector euler_fwd(size_t N,double t_0,double t_end,Vector y_0,const OdeFunction< Vector >& func); // (A N-dimensional real vector, *not* `std::vector`, see above) 

Si la fonction avait d'abord été conçue autour d'une interface, alors vous auriez dû hériter de cet ABC. Vous avez maintenant cette option, ainsi que le pointeur de fonction, lambda ou tout autre object de fonction. The key here is that we must have operator()() , and we must be able to do use some arithmetic operators on its return type. Thus, the template machinery would break in this case if C++ did not have operator overloading.

The concept of separating interface from interface and being able to swap out the implementations is not insortingnsic to Object-Oriented Programming. I believe it’s an idea that was hatched in Component-Based Development like Microsoft COM. (See my answer on What is Component-Driven Development?) Growing up and learning C++, people were hyped out inheritance and polymorphism. It wasn’t until 90s people started to say “Program to an ‘interface’, not an ‘implementation'” and “Favor ‘object composition’ over ‘class inheritance’.” (both of which quoted from GoF by the way).

Then Java came along with built-in garbage collector and interface keyword, and all of a sudden it became practical to actually separate interface and implementation. Before you know it the idea became part of the OO. C++, templates, and STL predates all of this.