Bonjour,
Avec les nouveauté du C++11. Je me demandais si il est possible de faire de l'inversion de contrôle sans avoir à faire de l'allocation dynamique.
Je pensais à l'utilisation de std::move et héritage mais ça ne marche pas.
Vous avez une idée?
Bonjour,
Avec les nouveauté du C++11. Je me demandais si il est possible de faire de l'inversion de contrôle sans avoir à faire de l'allocation dynamique.
Je pensais à l'utilisation de std::move et héritage mais ça ne marche pas.
Vous avez une idée?
Bonjour,
Ça veut dire quoi "faire de l'inversion de controle" ?
Hello,
L'idée c'est d'avoir une décorrélation entre l'instanciation d'un objet et sont utilisation.
On pourrait utiliser l'allocation dynamique mais je me dis que pour des objets qui nécessitent peu de mémoire ce serait beaucoup moins intéressant d'utiliser la heap.
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11 class IAnimal { public: virtual void Mange() = 0; }; void Programme() { IAnimal iAnimal = GetAnimal(...); animal.Mange(); }
selon ton exemple, cela s'appelle juste du polymorphisme... Pour réaliser une résolution dynamique des liens tu n'as pas d'autre choix d'utiliser un pointeur ou une référence.
Non, l'inversion de contrôle ne se réduit pas au polymorphisme, elle consiste à décharger le code client utilisateur d'une hiérarchie de la responsabilité de choisir ce qui est instancié. La cas idéal est de ne donner au code client que la connaissance d'un interface (donc en c++ une classe abstraite ne contenant que des fonctions virtuelles pures). Un code dédié se charge de retourner des instances de classes d'implémentation adaptées au cas (par exemple à la configuration, ou en fonction du chargement de plugins).
La notion d'inversion de contrôle est donc plus liée à la notion de factory.
A priori, l'inversion de contrôle ne nécessite pas en soi l'allocation dynamique, puisqu'on peut renvoyer un pointeur ou une référence à partir d'un objet membre à condition de ne pas faire de c*** avec les durées de vie...
Après, si tu cherches à configurer tes factories en fonction de paramètres runtime, ça devient plus difficile d'éviter l'allocation dynamique, mais ce n'est pas forcément infaisable.
La je comprends mieux ^^Un mix de Strategy et Factory pattern, pour etre precis.
Salut,Il n'y a rien à faire:
Une grande partie de l'inversion de contrôle passe par le polymorphisme: le fait qu'un comportement qui est disponible au niveau de la classe de base puisse être adapté au type réel de l'objet manipulé, qui correspond à un type dérivé de la classe de base.
Le fait est que tu ne peux profiter du polymorphisme, en C++, qu'au travers de pointeurs ou de références!
Tu pourrais donc avoir un code proche de
pour autant que GetAnimal renvoie une référence (éventuellement constante) sur une instance particulière de IAnimal.
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11 class IAnimal { public: virtual void Mange() = 0; }; void Programme() { IAnimal /* const */ & iAnimal = GetAnimal(...); animal.Mange(); }
Le problème vient alors de la durée de vie des variables (non statiques) pour lesquelles on n'a pas eu recours à l'allocation dynamique de la mémoire d'une part et à la sémantique d'entité qui est associée aux classes qui interviennent dans une hiérarchie d'objet d'autre part.
En effet, les variables non statiques pour lesquelles on n'a pas recours à l'allocation dynamique sont automatiquement détruites lorsque l'on atteint l'accolade fermante '}' de la portée dans laquelle les variables sont déclarées.
Si tu as une classe Chien héritant de IAnimal sous la forme de
un code proche de
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4 clas Chien : public IAnimal{ public: /* ... */ };
te claquera systématiquement dans les pattes parce que la référence renvoyée fera référence à... un objet qui a été détruit.
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4 IAnimal & GetAnimal(/* ... */){ Chien c(/*... */ ); return c; } // c est détruit ici
On pourrait envisager de créer une instance statique de chien, sous la forme de
mais, à chaque fois que tu ne pourrais alors avoir qu'un et un seul chien.
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4 IAnimal & GetAnimal(/* ... */){ static Chien c(/*... */ ); return c; }
L'alternative est alors de créer un type contenant un tableau de chiens et de veiller à placer l'objet à chaque fois dans le tableau, sous la forme de
parce qu'il n'est pas possible de créer une collection de références.
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14 class AnimalFactory{ public: IAnimal& getAnimal(/* ...*/){ if(/* c'est un chien */ ){ return createChien(/* ... */); } } private: IAnimal & createChien(/*... */){ chiens_.push_back(Chien(/* ... */ ); return chiens_[chiens_.size()-1]; } std::vector<Chien> chiens_; };
Tu serais donc obligé de maintenir une collection pour chaque type d'animal spécifique, et tu serait malgré tout limité, pour la durée de validité de ta référence, à la durée de vie de la factory sous une forme proche de
Mais, de plus, ce code se heurte de plein fouet à la sémantique d'entité associée aux classes qui interviennent dans une hiérarchie de classes!
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7 int main(){ if(une condition){ AnimalFactory factory; IAnimal & animal = factory.GetAnimal(/* ... */); } // factory est détruit ici. La référence animal est invalidée à cause de ca return 0; }
En effet, une classe ayant sémantique d'entité n'est, par nature, ni copiable ni assignable.
Cela signifie que ta classe IAnimal devrait prendre la forme de (C++11 inside)
et que toutes les classes qui héritent (de manière directe ou indirecte) de IAnimal seront elles aussi non copiable et non assignable.
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7 class IAnimal { public: IAnimal(IAnimal const &) = delete; IAnimal& operator = (IAnimal const &) = delete; virtual void Mange() = 0; };
Sans recourir à la sémantique de mouvement, il te sera impossible de rajouter ton chien dans la collection de chien, parce qu'il n'est pas copiable.
Et le fait d'utiliser un tableau "C style" comme collection ne changera rien: un chien n'est pas assignable
Tu te retrouves donc "obligé", si tu veux disposer de différents animaux en les connaissant comme des IAnimal, de gérer leur durée de vie sous la forme de pointeurs, et donc de recourir à l'allocation dynamique.
Ceci dit, C++11 dispose maintenant de classes de pointeurs RAIIsantes, et il serait sans doute intéressant d'envisager d'utiliser un std::unique_ptr<Chien> (ou std::unique_ptr<IAnimal>, selon ce qui t'intéresse le plus) dans la collection qui maintient les différentes instances de chiens (ou d'animaux)
L'idée est alors d'utiliser le pointeur nu (IAnimal * ) renvoyé par la fonction get de unique_ptr chaque fois que tu a "juste" besoin de pouvoir disposer de l'objet, et de te dire que tu n'as pas besoin de t'inquiéter de la durée de vie de l'objet sous-jacent![]()
A méditer: La solution la plus simple est toujours la moins compliquée
Ce qui se conçoit bien s'énonce clairement, et les mots pour le dire vous viennent aisément. Nicolas Boileau
Compiler Gcc sous windows avec MinGW
Coder efficacement en C++ : dans les bacs le 17 février 2014
mon tout nouveau blog
@koala01
Si on parle de fabriques produisant n entités, je te suis, lespointeursallocations dynamiques sont indispensables.
Par contre, pas mal d'IOC dans mon expérience se rapporte à la génération au runtime d'instances de services ou approchant. Assez souvent ces services n'ont pas besoin d'exister en multiples exemplaires, le polymorphisme se rapportant plus au choix runtime d'une implémentation ou d'une autre en fonction de paramètres (configuration, arguments d'appel, etc) et dans ce cas je pense qu'on peut imaginer des instances locales à la fonction de plus haut niveau qui après tout représente la durée de vie du traitement ou de l'application, et qui sont transmises aux objets clients (qui eux-mêmes ne dureront pas plus longtemps) sous forme de références.
ok si je comprend bien
C'est possible de faire de l'inversion de contrôle sans avoir a taper sur la heap seulement si je veux travailler sur des instances de service...
ça ressemble un peu au Proxy non?
j'ai écris ce bout de code pour avoir une idée de ce que ça donnerait:
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35 #include <iostream> #include<string> #include<functional> using namespace std; class I { public: virtual void F1(int) = 0; }; class ProxyI: public I { function<void (int)> _f; public: ProxyI(function<void (int)> f):_f(f){} ProxyI(const ProxyI &p):_f(p._f){} ProxyI(ProxyI&& p):_f(move(p._f)){} virtual void F1(int i){_f(i);} }; ProxyI GetProxy() { ProxyI p([](int i) { cout << "Proxy " << i << endl; }); return p; } int main() { ProxyI p = GetProxy(); p.F1(4); return 0; }
Disons que si (et seulement si) tu peux te contenter d'une seule et unique instance de chaque type, tu peux assez facilement t'en sortir en créant des variables statiques sous une forme qui pourrait etre proche de
qui pourrait t'assurer que chaque animal n'est construit que la première fois que tu en auras besoin et qui t'assurera de la pérennité de la référence renvoyée.
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31 class Factory{ public: IAnimal & getType(/*... */){ if(veau) return getVeau(/* ... */); if(vache) return getVache(/* ... */); if(cochon) return getCochon(/* ... */); if(cheval) return getCheval(/* ... */); /*...*/ } private: IAnimal & getVeau(/*...*/){ static Veau v(/*...*/); return v; } IAnimal & getVache(/*...*/){ static Vache v(/*...*/); return v; } IAnimal & getCochon(/*...*/){ static Cochon c(/*...*/); return c; } IAnimal & getCheval(/*...*/){ static Cheval c(/*...*/); return c; } };
Une autre solution pourrait être d'avoir une classe (Factory) qui disposerait explicitement d'un membre de chaque type, et de faire en sorte que la fonction renvoie la référence sur le type demandé.
On serait alors dans le cadre où la référence ne serait accessible qu'au mieux durant la durée de vie de la Factory
L'inconvénient que l'on peut pointer quant à cette solution étant que tous les animaux de la cour et de la basse-cour seront créé à chaque fois que tu auras besoin de l'un seul d'entre eux
Maintenant, il y a quand même une question qui me brule les lèvres:
Pourquoi tiens tu tellement à éviter l'allocation dynamique
Est-ce du à des spécifications particulières, à ta crainte de voir les fuites mémoires s'entasser au point de rendre le système instable
ou à ta religion qui te l'interdit
![]()
![]()
A méditer: La solution la plus simple est toujours la moins compliquée
Ce qui se conçoit bien s'énonce clairement, et les mots pour le dire vous viennent aisément. Nicolas Boileau
Compiler Gcc sous windows avec MinGW
Coder efficacement en C++ : dans les bacs le 17 février 2014
mon tout nouveau blog
Oui c'est vrai le gros problème de l'utilisation de la stack pour instancier des objets est la durée de vie de ces objets. Donc l'utilisation du mot clef static réglerait ce problème mais on serait obligé d'avoir qu'une seule instance pour cet objet.
Merci à tous pour vos solutions.
Pour répondre à ta question koala01 c'est juste par curiosité.
En fait l'allocation dynamique est plus coûteuse en terme de temps d’exécution que l'utilisation de la stack. Si pour une raison ou pour une autre je me retrouve à créer dynamiquement des petits objets qui ont une durée de vie très courte.
Ex:
Je me dis que si on peut passer par la stack dans un cas comme celui là on gagnerait beaucoup sur le temps d'exécution.
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7 while(...) { ... shared_ptr<IPoint> sp_iPoint = factory.Get(...); ... }
Ou simplement la main peut construire si on parle de services, et alors la durée de vie des objets dépend de la durée d'exécution de la main...l'IOC sert aussi à augmenter la réutilisabilité et la testabilité, sans forcément parler de décision runtime. Et même au runtime:
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77 class UserDao{ public: virtual std::unique_ptr<User> getUser(int id) const=0; virtual ~UserDao(){}; }; class GizmoDao{ public: virtual std::unique_ptr<User> getGizmo(int id) const=0; virtual ~GizmoDao(){} }; class DaoFactory{ public: virtual const UserDao & getUserDao() const=0; virtual const GizmoDao & getGizmoDao() const=0; virtual ~DaoFactory(){}; }; class DbUserDao: public UserDao{ public: const User & getUser(nt id) const; }; class DbGizmoDao: public UserDao{ public: const User & getGizmo(nt id) const; }; class DbDaoFactory{ private: DbUserDao userDaoImpl; DbGizmoDao userGizmoDaoImpl; public:/*inline pour illustrer mon propos*/ const UserDao & getUserDao() const { return this->userDaoImpl; }; const GizmoDao & getGizmoDao() const { return this->gizmoDaoImpl; }; }; class XmlUserDao: public UserDao{ public: const User & getUser(nt id) const; }; class XmlGizmoDao: public UserDao{ public: const User & getGizmo(nt id) const; }; class XmlDaoFactory{ private: XmlUserDao userDaoImpl; XmlGizmoDao userGizmoDaoImpl; public:/*inline pour illustrer mon propos*/ const UserDao & getUserDao() const { return this->userDaoImpl; }; const GizmoDao & getGizmoDao() const { return this->gizmoDaoImpl; }; }; int main(int argc,char** argv) { if(std::string("Db")==argv[1]) { useDaos(DbDaoFactory()); }else if(std::string("Xml")==argv[1]) { useDaos(XmlDaoFactory()); } } void useDaos(const DaoFactory & factory) { /* là on s'en sert tout son saoul...*/ }
Tu ne dois pas commencer par te demander si c'est plus coûteux avec une "heaper" que avec une "stacker". La différence doit ce trouver au nanosecond prêt sur un objet et tu gagneras en performance à 99.99% dans d'autres optimisations que celle la. Par exemple, tu dis que tu dois créer de nombreux petits objets -> pré-allocation d'un buffer de x petits objets ( une seule allocation dynamique - réutilisation multiple )En fait l'allocation dynamique est plus coûteuse en terme de temps d’exécution que l'utilisation de la stack. Si pour une raison ou pour une autre je me retrouve à créer dynamiquement des petits objets qui ont une durée de vie très courte.
Il est préférable d'attendre d'être dans la situation, puis de prouver par des mesures que c'est la source du problème, avant de faire des optimisation aussi "violentes".
Par contre, à ce moment là l'avantage d'avoir utilisé une factory sera de rendre la modification plus gérable (la construction étant centralisée).
Ceci dit, l'IoC, dans mon expérience, s'applique plutôt à des objets ayant des liens forts (détenant des références les uns aux autres), donc peu à des créations massives d'instances éphémères.
Partager