Je veux dire, mis à part son nom obligatoire (la bibliothèque de modèles standard) ...
C++ avait initialement pour objectif de présenter OOP - des concepts en C. Autrement dit, vous pouvez dire ce qu'une entité spécifique peut et ne peut pas faire (quelle que soit la méthode utilisée) en fonction de sa classe et de sa hiérarchie. Certaines compositions de capacités sont plus difficiles à décrire de cette manière en raison de la problématique de l'héritage multiple et du fait que C++ supporte le concept d'interfaces de manière quelque peu maladroite (par rapport à Java, etc.), mais il existe déjà (et pourrait être amélioré).
Et puis les modèles sont entrés en jeu, avec la STL. Le TSL semblait reprendre les concepts classiques OOP et les jeter à la poubelle, en utilisant des modèles.
Il convient de distinguer les cas où les modèles sont utilisés pour généraliser des types où les types eux-mêmes ne sont pas pertinents pour le fonctionnement du modèle (conteneurs, par exemple). Avoir un vector<int>
est parfaitement logique.
Cependant, dans de nombreux autres cas (itérateurs et algorithmes), les types basés sur un modèle sont supposés suivre un "concept" (Iterator d'entrée, Iterator, etc.) où les détails du concept sont entièrement définis par la mise en oeuvre du modèle. fonction/classe, et non par la classe du type utilisé avec le modèle, ce qui est un peu anti-usage de la POO.
Par exemple, vous pouvez dire à la fonction:
void MyFunc(ForwardIterator<...> *I);
Mise à jour: Comme il n'était pas clair dans la question initiale, ForwardIterator peut également être modélisé pour autoriser tout type ForwardIterator. Le contraire est d'avoir ForwardIterator en tant que concept.
attend un Forward Iterator uniquement en consultant sa définition, où vous devrez examiner l'implémentation ou la documentation pour:
template <typename Type> void MyFunc(Type *I);
Je peux faire deux affirmations en faveur de l'utilisation de modèles: le code compilé peut être rendu plus efficace en compilant le modèle sur mesure pour chaque type utilisé, au lieu d'utiliser vtables. Et le fait que les modèles peuvent être utilisés avec des types natifs.
Cependant, je cherche une raison plus profonde pourquoi abandonner le classique OOP en faveur de la création de modèles pour le TSL? (En supposant que vous lisiez si loin: P)
La réponse courte est "parce que C++ est passé à autre chose". Oui, à la fin des années 70, Stroustrup avait l’intention de créer un C mis à niveau doté des fonctionnalités OOP, mais cela fait longtemps. Au moment de la normalisation de la langue en 1998, cette langue n'était plus OOP. C'était un langage multi-paradigme. Il supportait certes un peu le code OOP, mais il comportait également un langage de modèle turing-complete, il permettait la métaprogrammation au moment de la compilation et les gens avaient découvert la programmation générique. Tout à coup, OOP ne semblait plus très important. Pas lorsque nous pouvons écrire et du code plus simple et plus concis en utilisant des techniques disponibles via des modèles et une programmation générique.
OOP 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 ils ont été inventés. Mais honnêtement, ce n’est pas tout. Dans de nombreux cas, il est maladroit et prolixe et ne favorise pas vraiment le code réutilisable ni la modularité.
C'est pourquoi la communauté C++ s'intéresse aujourd'hui beaucoup plus à la programmation générique et pourquoi tout le monde commence enfin à comprendre que la programmation fonctionnelle est également assez intelligente. OOP seul n'est pas beau à voir.
Essayez de dessiner un graphe de dépendance d’un STL hypothétique "OOP-ified". Combien de classes devraient se connaître les unes les autres? Il y aurait un lot de dépendances. Seriez-vous en mesure d'inclure uniquement l'en-tête vector
, sans que vous obteniez également iterator
ou même iostream
? La STL facilite les choses. Un vecteur connaît le type d'itérateur qu'il définit, et c'est tout. Les algorithmes STL connaissent rien. Ils n'ont même pas besoin d'inclure un en-tête d'itérateur, même s'ils acceptent tous les itérateurs en tant que paramètres. Lequel est plus modulaire alors?
Le STL peut ne pas suivre les règles de OOP telles que définies par Java, mais n'atteint-il pas le objectifs de OOP? Ne permet-il pas une réutilisation, un couplage faible, une modularité et une encapsulation?
Et ne réalise-t-il pas ces objectifs meilleurs qu'une version utilisant la programmation orientée objet?
En ce qui concerne la raison pour laquelle le TSL a été adopté dans la langue, plusieurs événements ont conduit 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 être une bonne idée de pouvoir écrire des choses comme "des conteneurs de type T" sans jeter le type de sécurité. Bien entendu, la mise en œuvre sur laquelle ils se sont mis était beaucoup plus complexe et puissante.
Ensuite, les gens ont découvert que le mécanisme de modèle 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'une inspirée de la programmation fonctionnelle et l'autre utilisant toutes les nouvelles fonctionnalités de C++.
Il l'a présenté au comité du langage C++, qui a mis un certain temps à s'y habituer, car il avait l'air si étrange et si différent, mais a finalement réalisé que cela fonctionnait mieux que les équivalents OOP traditionnels qu'ils auraient. inclure autrement. Ils ont donc fait quelques ajustements et l’ont intégré à la bibliothèque standard.
Ce n'était pas un choix idéologique, ce n'était pas un choix politique de "voulons-nous être OOP ou non", mais un choix très pragmatique. Ils ont évalué la bibliothèque et ont constaté que cela fonctionnait très bien.
Quoi qu'il en soit, les deux raisons que vous avez mentionnées 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 équivalent roulé à la main, les utilisateurs ne l'utiliseront pas. Cela réduirait la productivité, augmenterait les risques de bugs et serait globalement une mauvaise idée.
Et le STL doit pour travailler avec les types primitifs, parce que les types primitifs sont tout ce que vous avez en C, et ils constituent une partie importante des deux langages. Si le STL ne fonctionnait pas avec des tableaux natifs, il serait inutile .
Votre question repose sur une forte hypothèse selon laquelle OOP est "préférable". Je suis curieux d'entendre pourquoi. Vous demandez pourquoi ils ont "abandonné la POO classique". Je me demande pourquoi ils auraient dû rester avec. Quels avantages aurait-il eu?
La réponse la plus directe à ce que je pense que vous demandez/plaignez 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 OOP, 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 appelé C++ 0x), certaines choses peuvent même être programmées de manière fonctionnelle.
Les concepteurs de C++ voient cela comme un avantage. Ils soutiendraient donc que contraindre le C++ à agir comme un langage purement OOP lorsque la programmation générique résout mieux le problème et, enfin, plus génériquement , serait une étape en arrière.
D'après ce que j'ai compris, Stroustrup avait initialement opté pour un concept de conteneur de type «POO» et ne voyait en fait aucun autre moyen de le faire. Alexander Stepanov est le responsable du TSL et ses objectifs n'incluaient pas "le rendre orienté objet" :
C'est le point fondamental: les algorithmes sont définis sur des structures algébriques. Il m'a fallu encore quelques années pour comprendre qu'il fallait élargir la notion de structure en ajoutant des exigences de complexité aux axiomes classiques. ... Je pense que les théories sur les itérateurs sont aussi fondamentales en informatique que les théories des anneaux ou des espaces de Banach sont au cœur des mathématiques. Chaque fois que je regardais un algorithme, j'essayais de trouver une structure sur laquelle il est défini. Je voulais donc décrire les algorithmes de façon générique. C'est ce que j'aime faire. Je peux passer un mois à travailler sur un algorithme bien connu pour tenter de trouver sa représentation générique. ...
STL, du moins pour moi, représente le seul moyen de programmation possible. Il est en effet très différent de la programmation C++ telle qu’elle a été présentée et est toujours présentée dans la plupart des manuels. Mais, voyez-vous, je n’essayais pas de programmer en C++, j’essayais de trouver le bon moyen 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 pour l'héritage et les 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 étaient très ridicules.
(Il explique pourquoi l'héritage et les virtuels - par exemple, une conception orientée objet "étaient fondamentalement défectueux et ne devraient pas être utilisés" dans la suite de l'entretien).
Une fois que Stepanov a présenté sa bibliothèque à Stroustrup, Stroustrup et d’autres ont fait des efforts herculéens pour l’intégrer au standard ISO C++ (même entretien):
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é à apporter des modifications au TSL que je ne ferais jamais pour quelqu'un d'autre ... c'est la personne la plus déterminée que je connaisse. Il fait avancer les choses. Il lui fallut un certain temps pour comprendre en quoi consistait le TSL, mais quand il le comprit, il était prêt à faire avancer les choses. Il a également apporté sa contribution à STL en défendant l'idée que plus d'une méthode de programmation était valable - contre toutes les critiques et le battage médiatique depuis plus d'une décennie, et en recherchant une combinaison de flexibilité, d'efficacité, de surcharge et de sécurité des caractères modèles qui ont rendu possible STL. Je voudrais dire très clairement que Bjarne est le concepteur de langage par excellence de ma génération.
La réponse se trouve dans cet interview avec Stepanov, l'auteur du TSL:
Oui. STL n'est pas orienté objet. JE pense que l'objet est orienté est presque autant d'un canular qu'un artificiel Intelligence. Je n'ai pas encore vu un morceau de code intéressant qui vient de ces OO personnes.
Pourquoi une conception OOP pure dans une bibliothèque de structures de données et d’algorithmes serait-elle meilleure?! OOP 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 polymorphisme d’exécution, c’est un avantage pour STL d’implémenter la bibliothèque à l’aide de polymorphisme statique, c’est-à-dire efficacité . Essayez d’écrire un type générique de type Sort ou Distance ou un algorithme quelconque qui s'applique à TOUS les conteneurs! .votre tri en Java appelle des fonctions dynamiques à n niveaux à exécuter!
Vous avez besoin de choses stupides comme Boxing et Unboxing pour masquer les mauvaises hypothèses des langues dites Pure OOP.
Le seul problème que je vois avec STL, et les modèles en général, sont les terribles messages d'erreur ., Qui seront résolus à l'aide de Concepts en C++ 0X.
Comparer STL aux collections de Java, c'est comme comparer le Taj Mahal à ma maison :)
les types basés sur des modèles sont supposés suivre un "concept" (Itérateur d'entrée, Forward Itérateur, etc ...) où le réel les détails du concept sont définis entièrement par la mise en œuvre du modèle de fonction/classe, et non par la classe du type utilisé avec le modèle, qui est un peu anti-usage de la POO.
Je pense que vous avez mal compris l'utilisation prévue des concepts par 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 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 Entrée, Sortie et Itérateur trivial pour tout voir).
Ce document est une interface parfaitement bonne et "les détails du concept" sont définis ici. Ils ne sont pas définis par les implémentations de Forward Iterators, ni par les algorithmes qui utilisent Forward Iterators.
Les différences de traitement des interfaces entre STL et Java sont de trois ordres:
1) STL définit des expressions valides à l'aide de l'objet, alors 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 nécessairement le cas.
2) Les interfaces Java sont des objets d'exécution, alors que les concepts STL ne sont pas visibles au moment de 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 qui l'indique.
Cette troisième partie est si vous aimez une sorte de "typage de canard" (à la compilation): les interfaces peuvent être implicites. En Java, les interfaces sont quelque peu explicites: une classe "est" Iterable si et seulement si dit 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 seul un code supplémentaire (tests unitaires) peut vous indiquer 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 utilisez le préprocesseur de typage renforcé), vous obtenez de l'aide du compilateur. Si un programmeur requiert une déclaration explicite des 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 ne soit trop détaillé). Ce qui manque, comparé à Java, c'est un modèle unique que je peux instancier avec mon type et qui 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 pratique, mais ce n'est pas le cœur de OOP (et cela ne teste toujours pas la sémantique, et le code pour tester la sémantique vérifierait naturellement aussi la validité des expressions en question).
STL peut ou peut ne pas être suffisamment OO à votre goût, mais il sépare certainement l'interface proprement de l'implémentation. La capacité de Java à réfléchir sur les interfaces est insuffisante, et les violations des exigences d'interface sont signalées différemment.
vous pouvez dire à la fonction ... attend un Iterator Forward que par En regardant sa définition, où vous auriez besoin soit de regarder le la mise en oeuvre ou la documentation pour ...
Personnellement, je pense que les types implicites sont une force, lorsqu'ils sont utilisés correctement. L'algorithme dit ce qu'il fait avec ses paramètres de gabarit, et l'implémenteur s'assure que ces choses fonctionnent: c'est exactement le dénominateur commun de ce que "les interfaces" doivent faire. De plus, avec STL, il est peu probable que vous utilisiez, par exemple, std::copy
en recherchant sa déclaration directe dans un fichier d’en-tête. Les programmeurs devraient déterminer ce qu’une fonction prend d’après sa documentation, et pas seulement la signature de la fonction. Cela est vrai en C++, Python ou Java. Il y a des limites à ce que l'on peut réaliser avec la dactylographie dans n'importe quelle langue, et essayer d'utiliser la dactylographie 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 paramètres de modèle de manière à préciser 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 anticipées plus informatives. Vous devez savoir plus de choses que ce qui peut être encapsulé dans les types de paramètres. Vous devez donc lire la documentation. (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 a besoin d'assez d'espace pour un certain nombre de sorties en fonction de la taille de la plage d'entrée et peut-être de ses valeurs. Essayez de taper fortement. ).
Voici Bjarne sur les interfaces explicitement déclarées: http://www.artima.com/cppsource/cpp0xP.html
En procédant de la sorte, vous pouvez implémenter une interface sans savoir que celle-ci existe. Ou bien quelqu'un peut écrire délibérément une interface de sorte que votre classe l'implémente, après avoir consulté vos docs pour voir qu'ils ne demandent rien de ce que vous n'avez pas déjà fait. C'est flexible.
Looking at it the other way around, with duck typing you can implement an interface without knowing that the interface exists. Or someone can write an interface deliberately such that your class implements it, having consulted your docs to see that they don't ask for anything you don't already do. That's flexible.
"La POO ne signifie pour moi que la messagerie, la rétention et la protection locales et le masquage du processus d'état, et la liaison tardive de toutes choses. Cela peut être fait dans Smalltalk et dans LISP. Je ne suis pas au courant d'eux. " - Alan Kay, créateur de Smalltalk.
C++, Java et la plupart des autres langages sont assez éloignés de la POO classique. Cela dit, argumenter pour des idéologies n’est pas terriblement productif. C++ n'est en aucun cas pur, il implémente donc des fonctionnalités qui semblent pragmatiques à l'époque.
STL a commencé avec l'intention de fournir une grande bibliothèque couvrant l'algorithme le plus couramment utilisé - avec pour objectif le comportement consitent et performance. Le modèle était un facteur clé pour rendre cette mise en œuvre et cet objectif 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 professionnelle et son choix à l’égard d’une vaste bibliothèque d’algorithmes, qui a par la suite évolué en LIST.
Parlez-nous de votre intérêt à long terme pour la programmation générique
..... Ensuite, on m'a 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, j'ai dit que je pouvais le faire. Mais je ne pouvais pas le faire en C++, car en 1987, le C++ n’avait pas de modèles, ce qui est essentiel pour permettre ce style de programmation. L'héritage était le seul mécanisme permettant d'obtenir la généricité et ce n'était pas suffisant.
Même maintenant, l'héritage C++ n'est pas d'une grande utilité pour la programmation générique. Discutons pourquoi. De nombreuses personnes ont tenté d'utiliser l'héritage pour implémenter des structures de données et des classes de conteneur. Comme nous le savons maintenant, il y a eu peu ou pas de tentatives réussies. L'héritage C++ et le style de programmation qui lui est associé sont considérablement limités. Il est impossible de mettre en œuvre une conception qui inclut une chose aussi triviale que l'égalité. 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 prenant les animaux comme exemple (OO les gens aiment les animaux), définissez un mammifère et dérivez une girafe d'un mammifère. Définissez ensuite un compagnon de fonction membre, où l'animal s'accouple avec l'animal et renvoie un animal. Ensuite, vous dérivez girafe d’animal et, bien sûr, il a une fonction compagnon où la girafe s’accouple avec l’animal et renvoie un animal. Ce n'est certainement pas ce que vous voulez. Bien que l'accouplement ne soit pas très important pour les programmeurs C++, l'égalité l'est. Je ne connais pas un seul algorithme où l'égalité n'est pas utilisée.
Le problème de base avec
void MyFunc(ForwardIterator *I);
comment obtenir en toute sécurité le type de chose que l'itérateur renvoie? Avec les modèles, ceci est fait pour vous au moment de la compilation.
Un instant, considérons la bibliothèque standard comme une base de données de collections et d’algorithmes.
Si vous avez étudié l'histoire des bases de données, vous savez sûrement qu'au début, les bases de données étaient principalement "hiérarchiques". Les bases de données hiérarchiques correspondaient très étroitement à la POO classique - en particulier à la variété à héritage unique, telle que celle utilisée par Smalltalk.
Au fil du temps, il est devenu évident que des bases de données hiérarchiques pouvaient être utilisées pour modéliser presque tout, mais dans certains cas, le modèle à héritage unique était assez limitatif. 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'héritage multiple. C++ supporte complètement l'héritage multiple, alors que Java supporte 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èle hiérarchique et de modèle de réseau ont pour la plupart disparu de l'utilisation générale (bien que quelques-unes restent dans des niches assez spécifiques). Dans la plupart des cas, elles ont été remplacées par des bases de données relationnelles.
Une grande partie de la raison pour laquelle les bases de données relationnelles ont pris le dessus était la polyvalence. Le modèle relationnel est fonctionnellement un sur-ensemble du modèle de réseau (qui, à son tour, est un sur-ensemble du modèle hiérarchique).
C++ a largement suivi le même chemin. La correspondance entre l'héritage unique et le modèle hiérarchique et entre l'héritage 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 c'est quand même assez proche.
Je n'ai pas vu de preuve formelle à ce sujet, mais je pense que les capacités des modèles sont un sur-ensemble de celles fournies par l'héritage multiple (qui est clairement un sur-ensemble d'inerhitance unique). La difficulté réside dans le fait que les modèles sont principalement liés de manière statique, c'est-à-dire que toutes les liaisons ont lieu au moment de la compilation, pas au moment de l'exécution. En tant que tel, une preuve formelle que l'héritage fournit un sur-ensemble des capacités de l'héritage peut être quelque peu difficile et complexe (ou même impossible).
Quoi qu'il en soit, je pense que c'est la principale raison pour laquelle le C++ n'utilise pas l'héritage pour ses conteneurs - il n'y a aucune raison de le faire, car l'héritage ne fournit qu'un sous-ensemble des fonctionnalités fournies par les modèles. Comme les modèles sont fondamentalement une nécessité dans certains cas, ils peuvent aussi bien être utilisés presque partout.
Comment faites-vous des comparaisons avec ForwardIterator *? Comment vérifier si l’objet que vous possédez correspond à ce que vous recherchez ou si vous l’avez oublié?
La plupart du temps, j'utiliserais quelque chose comme ceci:
void MyFunc(ForwardIterator<MyType>& i)
ce qui signifie que je sais que je pointe vers MyType, et je sais comment les comparer. Bien que cela 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. Dans l'état actuel des langages de programmation orientés objet, il est nécessaire d'utiliser le modèle de visiteur pour traiter de tels problèmes, et true OOP doit prendre en charge plusieurs liaisons dynamiques. Voir Multi-méthodes ouvertes pour 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 polymorphisme d'exécution. Par exemple
template<class Value,class T>
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<N;++k)
{y_0+=func(t_0 + k*dt,y_0)*dt;}
return y_0;
}
Notez que cette fonction fonctionnera également si Value
est un vecteur (not std :: vector, qui devrait être appelé std::dynamic_array
pour éviter toute confusion)
Si func
est petit, cette fonction gagnera beaucoup en ligne. 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).
Vous avez maintenant une grande expression dans func
et vous utilisez le résolveur ODE à plusieurs 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 ajouter currying 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 double
, pourquoi ne pas écrire l'interface en tant que modèle:
template<class Value=double>
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<double>& func);
template vec4_t<double> euler_fwd(size_t N,double t_0,double t_end,vec4_t<double> y_0,const OdeFunction< vec4_t<double> >& func); // (Native AVX vector with four components)
template vec8_t<float> euler_fwd(size_t N,double t_0,double t_end,vec8_t<float> y_0,const OdeFunction< vec8_t<float> >& func); // (Native AVX vector with 8 components)
template Vector<double> euler_fwd(size_t N,double t_0,double t_end,Vector<double> y_0,const OdeFunction< Vector<double> >& func); // (A N-dimensional real vector, *not* `std::vector`, see above)
Si la fonction avait d'abord été conçue autour d'une interface, vous auriez été forcé d'hériter de cet ABC. Vous avez maintenant cette option, ainsi que le pointeur de fonction, lambda ou tout autre objet de fonction. La clé ici est que nous devons avoir operator()()
, et nous devons pouvoir utiliser certains opérateurs arithmétiques sur son type de retour. Ainsi, la machine modèle se briserait dans ce cas si C++ n’avait pas de surcharge d’opérateur.