Je suis conscient que la question n'est pas très claire, un petit bout de code pour illustrer :
class Base {
public:
Base() { this->init(); }
virtual void init() {}
};
class Derived : public Base {
public:
Derived() : Base() { }
void init() {}
};
class Derived2 : public Base {
public:
Derived2() : Base() { this->init(); }
void init() {}
};
[...]
Base b; // calls Base::init()
Derived d; // calls Base::init()
Derived2 d1: // calls Base::init() and Derived2::init();
Je crois comprendre assez bien les raisons pour lesquelles un constructeur ne peut pas appeler de fonctions virtuelles (parce que ces méthodes virtuelles pourraient tenter d'accéder à des éléments inconnus de la classe de base, par exemple), mais une «solution» pourrait être de savoir d'une manière ou d'une autre dans le constructeur si on est au bout de la chaine d'appel des constructeurs (si on est une «feuille» de l'arbre de la hiérarchie de classes) ou non, ce qui permettrait de faire quelque chose comme if (is_leave()) this->init();
dans tous les constructeurs).
L'autre solution est évidemment de définir à l'avance qui est une classe de base et qui est dérivé, en rendant toutes les classes non-dérivées virtuelles pures, et en dérivant une classe bidon de Base juste pour pouvoir l'instancier.
Il y a une vraie raison derrière ce problème, parce que j'aurais besoin d'initialiser un vecteur dont la taille dépend de la classe. Il y a plein de solutions non-élégantes au problème (redimensionner le vecteur, appeler la fonction virtuelle init() explicitement après le constructeur, …), mais j'ai peut-etre raté quelque chose d'évident?
# problème A -> B
Posté par nazcafan . Évalué à 2.
Je pense que ce que tu cherches à faire est impossible, et j'ai l'intuition que le vecteur dont la taille change selon la classe est une mauvaise solution B à un problème A que j'aimerais bien connaître.
# Ordre des constructeurs
Posté par Anthony Jaguenaud . Évalué à 3. Dernière modification le 10 juillet 2017 à 12:40.
Dans un cas simple (sans héritage multiple ni virtuel), les constructeurs sont appelés dans l’ordre.
Dans le cas Derived2 :
D’abord Base::Base() qui s’il appelle init ne peut appeler que Base::init (il ne peut avoir connaissance des autres, et la table des méthodes virtuel pointe pour l’instant sur sa fonction.
Ensuite Derived::Derived() qui s’il appelle init peut appeler soit la sienne Derived::init() soit celle de Base::init().
Ainsi de suite.
Si ton but est que chaque niveau de la hiérarchie appelle sa fonction d’init il n’y a pas de problème.
Une solution a ton problème pourrait-être d’utiliser une classe de callback sur destructeur.
Je n’ai pas défini le type de fct. On doit pouvoir utiliser
std::bind
pour inclure le this dans l’appel.Ensuite, tu crées ton constructeur un peu comme ça :
Le dernier appelé sera celui de plus haut niveau (le dernier exécuté). L’objet temporaire créé pour le passage de paramètre sera détruit à la sortie du constructeur et donc, hors optimisation, le destructeur de l’objet callback devrait être appelé. (je n’ai pas le temps de tester mon idée…).
Je ne sais pas si ça résous ton problème, mais je rejoins le commentaire au dessus pour dire que ça semble être un soucis d’analyse du problème.
Par contre, la fonction que tu utilises ne peut être que statique. Ou alors, tu dois enregistrer un pointeur vers ton objet et dans ce cas faire :
obj.fct()
[^] # Re: Ordre des constructeurs
Posté par arnaudus . Évalué à 2.
Ça pourrait bien être le cas. Ceci dit, il arrive bien souvent qu'un «problème d'analyse» ne soit pas vraiment distinguable d'une chose que C++ ne sait pas faire ; c'est clair que j'essaye de faire quelque chose qui n'est pas naturel en C++. Le principe de ce qui me semblerait naturel (vite fait):
La logique, c'est que Polygon sait se construire tout seul avec un code générique, il lui manque juste quelques infos auxquelles il pourrait accéder par des "getters" virtuels qui, eux, sont spéciques des classes dérivées. Finalement, ça revient à dire que je souhaite juste éviter quelque chose du style
Polygon * p = new Triangle(); p->init();
afin d'éviter de pouvoir laisser l'objet dans un état pas complètement construit.Le callback permet d'inverser artifciellement l'ordre de la construction, et je réalise que dans tous les cas un bricolage de ce style s'impose. Ça pourrait aussi être une Factory qui s'occuppe d'appeler le constructeur et init() dans la foulée, ou tout un tas d'autres «solutions» (par ex. une fonction statique Polygon* Construct()…). Plus j'y pense et plus j'ai l'impression que ce que j'essaye de faire, c'est de donner également le rôle de Factory à la classe de base, et que ça n'est pas une bonne idée…
Merci pour les pistes!
[^] # Re: Ordre des constructeurs
Posté par Anthony Jaguenaud . Évalué à 3.
Je comprends un peu mieux ce que tu veux faire… Ce n’est pas possible, car quand tu es dans le constructeur de polygone, le vtable (pointeur vers la table des fonctions virtuelles) est initialisé pour Polygone. Ce n’est qu’à la fin du constructeur de Polygone où le runtime modifie la vtable puis appelle le constructeur de Triangle.
Dans ce cas simple, la solution est de descendre l’information nb_de_point en paramètre au constructeur de Polygone.
Mais sinon, une méthode statique de construction de l’objet peut-être intéressante.
Pour comprendre l’ordre d’exécution, la vtable… tu peux ajouter des traces (un bête std::cout) et dump(this,sizeof(*this)) dans chaque constructeur. Ça devrait te donner une idée.
[^] # Re: Ordre des constructeurs
Posté par arnaudus . Évalué à 2.
Oui oui, c'est logique, je l'ai réalisé dès que j'ai repéré un bug lié à ce problème. Le principe de ma hiérarchie de classe est d'utiliser des méthodes génériques de la classe de base, et tout fonctionnait très bien jusqu'à ce que j'essaye de le faire aussi dans le constructeur.
Pour être honnête, j'ai même pensé à passer this en paramètre du constructeur de la classe de base, mais j'ai rapidement décidé que je ne voulais même pas savoir si c'était légal.
Je suis quand même un peu surpris que le C++ n'ait pas un mécanisme simple pour s'assurer qu'une fonction est exécutée exactement une fois lors de la construction, sans pour autant contraindre la hiérarchie de classes (typiquement, pour réserver de la mémoire). Dès que les classes filles appellent le constructeur des classes mères, on se retrouve à devoir choisir où réserver la mémoire ; soit dans le constructeur de la classe mère (et donc, de devoir se débrouiller pour passer des variables membres en paramètre du constructeur, ce qui est quand même étrange), soit dans le constructeur de la classe fille (ce qui rend presque mécaniquement la classe mère virtuelle). Disons que je comprends le pourquoi du comment techniquement, mais que ça me semble être un mécanisme légitime en POO…
[^] # Re: Ordre des constructeurs
Posté par Anthony Jaguenaud . Évalué à 2.
Personnellement, je m’étais fait avoir sur un destructeur qui appelait une fonction virtuelle pure… ça lance une belle exception ;-).
Si tu as ce besoin dans le constructeur, c’est que ta hiérarchie est mal répartie. Ce n’est pas un problème de C++, tu aurais le même type d’ennuis dans tous les langages objets que je connais.
Pour chaque classe tu dois savoir quelles sont ses responsabilités. Avoir les données minimales pour gérer ces responsabilités.
Si je reprends ton exemple de Polygone, il est logique que le polygone possède la liste des points. C’est même à lui de retourner le nb de points mais en regardant le nombre d’éléments dans la liste.
Par contre positionner les points c’est à la classe fille de le faire… via une nouvelle fonction de Polygone :
addPoint
.Ensuite, je ferais une classe spécifique comme ça :
J’ai tapé ça vite fait… je ne sais même pas si ça compile ;-) mais l’idée est là.
Ce n’est pas nécessaire, le this est le même dans toute la hiérarchie. Juste qu’une méthode parente n’a pas à appeler une méthode d’une fille, car sinon comment choisir ? Ex :
Comment peut-on construire var puisque var sera une
Fille2
ainsi qu’uneMere
. Mais pas uneFille
! Donc si ça compilait se serait catastrophique.Pour la suite je ne suis pas certains d’avoir tous saisi.
Je l’ai expliqué au dessus. Après tu peux faire une construction via une factory.
Je ne comprends pas.
Ton problème vient du fait que tu dois penser en terme de responsabilité :
Si a l’une de ses questions tu dois avoir des méthodes dans des classes qui hériteront c’est qu’il y a un soucis.
Je pense que tu découvre la POO et que tu as trouvé génial l’héritage et les fonctions virtuelles, mais il faut bien comprendre se qu’elles permettent et pas leur donner des pouvoirs qu’elles n’ont pas.
L’exemple classique s’est la figure géométrique que l’on veut déplacer. Il suffit d’appeler la fonction
dessiner
dans la fonctiondeplacer
. En rendant dessiner virtuelle, ça permet d’avoir une seule implémentation dedeplacer
.Je te conseillerai un livre très bien écrit : Programmer en langage c++ de Claude Delannoy. J’ai une très vieille version 4° édition 1998… il manque le c++03, c++11… néanmoins, la présentation du livre est très didactique et une fois assimilé les concepts de base, les informations glanées sur internet n’ont plus à être aussi didactique.
Finalement, j’ai fais un paver ! J’espère que ce n’est pas trop indigeste.
# Argument du constructeur ?
Posté par Space_e_man (site web personnel) . Évalué à 2.
Pourquoi pas utiliser un constructeur de Base avec argument ?
Suivre le flux des commentaires
Note : les commentaires appartiennent à celles et ceux qui les ont postés. Nous n’en sommes pas responsables.