Yo!
Je voudrais savoir si c'est problématique d'ajouter à une classe virtuelle pure
un attribut privé, alors que cette classe n'en possedait pas avant?
Quelles sont les précautions à prendre avec une telle classe?
Merci
Version imprimable
Yo!
Je voudrais savoir si c'est problématique d'ajouter à une classe virtuelle pure
un attribut privé, alors que cette classe n'en possedait pas avant?
Quelles sont les précautions à prendre avec une telle classe?
Merci
N’étant pas experte en C++, je penserais que si une classe est virtuelle tu peux bien ajouter des attributs privé cela ne changera grand chose. Apres je ne saisis pas pourquoi rajouter un attribut privé à un classe virtuelle, mais cela doit venir du fait que je suis trop imprégnée des interfaces java.
Par classe virtuelle pure, tu entends bien classe ou toutes les méthodes sont déclarée virtuelle pure ?
Dans ce cas, y mettre un attribut privée me parait peu utile, étant donnée qu'il ne pourra être accédé par personne, ou alors quelque chose m'échappe...
Ca peut avoir du sens.
Tu peux très bien définir qu'un cycle a deux roues, même si tu n'as pas encore défini s'il va avancer par propulsion humaine, thermique, ou électrique, et qu'il n'est donc pas encore constructible.
Dans ce cas, la propriété "a deux roues" est publique, pas privée.
1 / Une classe virtuelle pure n'a pas d'accesseurs qui ne sont pas eux-même virtuels purs (par définition).
2/ Sans accesseur concrets, il est impossible d'accéder aux propriétés privées d'un objet en C++ à partir d'une classe dérivée.
3/ La conclusion s'impose d'elle même : la seule chose que va faire la propriété privée, c'est prendre de la place en mémoire. Personne ne peut y avoir accès - comment, dès lors, lui trouver une utilité quelconque ?
Effectivement; j'avais un peu oublié le côté privé...
Salut,
D'abord, il faut faire attention aux termes utilisés...
On parle de classe abstraite lorsqu'une classe n'est pas instanciable (du fait de la présence de fonctions virtuelles pures) et de fonctions virtuelles pures lorsqu'une fonction est déclarée virtuelle mais que l'on n'est pas en mesure de fournir un comportement correct par manque de données.
les fonctions virtuelles pures sont repérables au fait que leur déclaration est suivie d'un = 0 avant le ";" final.
Rien ne t'empêche de déclarer (et de défininir !! ) une fonction qui n'est pas virtuelle ou qui est virtuelle (non pure ! ) dans une classe abstraite si... tu dispose de suffisemment d'informations pour lui donner un comportement cohérent ;)
A partir de là, il n'est pas du tout impensable de donner un membre privé à une classe abstraite pour autant que... l'une ou l'autre des fonctions non virtuelles pures l'utilise! (si aucune fonction non virutelle pure ne l'utilise, cela ne sert effectivement à rien)
Dans l'exemple de cycle que tu présente, nous pourrions dire que cycle hérite de... véhicule, qui dispose d'une fonction virtuelle pure nombreRoue, définie dans cycle, mais que cycle reste une classe abstraite parce que d'autres fonctions virtuelles pures subsistent dans cette classe:
Code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 class Vehicle { public: virtual Vehicle(); /* une série de fonctions virtuelles pures qui font de Vehicle * une classe abstraite */ virtual energyType energy() const = 0; virtual int weelsCount() const = 0; /* ... */ }; /* un bicycle a deux roues, mais un type d'énergie inconnu */ class Bicycle { public: virtual int weelsCount() const; /* Bicycle reste une classe abstraite parce que energy() est encore * virtuelle pure ;) */ private: int weelsCount_; };
J'ajouterai qu'il est possible de déclarer une méthode virtuelle comme étant pure (= 0) tout en fournissant une implémentation, utilisable une fois qu'on aura rendu l'instanciation possible dans une classe dérivée.
Pervers, certes..
Comme dit Scott Meyers, ça sert surtout à briller dans les dîners en ville.
C'est peut etre vicieux, mais cela peut, malgré tout, avoir une certaine utilité...
Ce que je vais proposer est tordu, mais, imagine une classe de base dont tu veux interdire l'instanciation, pour laquelle tu as une fonction:
et pour laquelle il n'y a pas vraiment de fonctions candidates "logiques" à être virtuelle pure.
- qui doit vérifier des préconditions / invariants
- qui doit etre réimplémentée dans les classes dérivées
- dont l'implémentation des classes dérivées doit utiliser les préconditions / invariants de la classe de base
- pourquoi pas, qui doit effectuer d'office certaines actions préparatoires / finales
Tu peux implémenter un pattern NVI sous une forme proche de
Code:
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 /* dans le fichier d'en-tête */ class MyClass { public: void foo() { /* cette ligne force la vérification des invariants */ MyClass::doFoo(); /* et celle-ci applique NVI pour les classes dérivées */ doFoo(); } private: virtual doFoo() = 0; } /* dans le fichier d'implémentaiton */ void MyClass::doFoo() { /* - vérification des invariants, * - vérification des préconditions * - travaux préparatoires */ } /* la classe dérivée */ class Derived : public MyClass { private: virtual void doFoo() { /* on n'a plus à s'inquiéter des préconditions, invariants ou autres * travaux préparatoires ;) */ } };
Bof. Il me parait plus propre d'avoir une fonction au nom qui suggère clairement une vérification des préconditions & invariants. Plutôt qu'une fonction au nom qui suggère autre chose, et que l'on pourrait de surcroit appeler par erreur depuis ses supplantations.
Cette astuce (fonction virtuelle pure définie) est surtout utile pour avoir des classes abstraites quand la seule fonction virtuelle d'une classe est son destructeur.
Ca m'étonne que personne ne l'ai encore dit dans cette discussion mais il y a autre chose (peut être que j'ai zappé si quelqu'un l'a dit) :
Si tu ajoutes des objets membres dans ta classe virtuelle, assures toi bien d'avoir son destructeur déclaré en virtual aussi.
Sinon, tu vas avoir des surprises. :aie:
L'idée c'est que tant que tu ne déclares pas le destructeur virtuel, il ne sera pas appelé par les destructeurs des classes enfants dans le cas d'une base virtuelle.
J'imagine que c'est déjà fait, mais dans le doute mieux vaut le rappeler.
Code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 struct A { void f(int a) { check_precond_f(a); do_f(a); } protected: ~A() {} private: void check_precond_f(int a) { if (a<42) throw std::range_exception(); } virtual void do_f(int a) = 0; }; struct B : A { private: virtual void do_f(int a); }; ... B b;
PS: "classe virtuelle" ne veut rien dire. Il y a
- classe abstraite
- classe abstraite dont tous les membres sont virtuels purs -- alors que le pattern NVI est pourtant bien mieux
- classe de base virtuelle, relativement à un héritage donné
Ouaip erreur a force de lire le titre du thread.
Je ne comprends pas le rapport avec ton exemple : d'après le premier post
Donc ce que je dis c'est qu'il faut mettre le destructeur virtuel si il ne l'est as encore sinon ces nouveaux attributs ne seront pas détruits...Citation:
Je voudrais savoir si c'est problématique d'ajouter à une classe virtuelle pure
un attribut privé, alors que cette classe n'en possedait pas avant?
Quelles sont les précautions à prendre avec une telle classe?
Si son interface définit une politique, ou une interface d'échange avec une API bien précise (dont le rôle n'est pas de fournir un moyen de détruire), je ne vois pas en quoi le destructeur non-public non-virtuel est en contradiction avec le besoin (mal exprimé)
En fait, c'est plutot l'inverse : si tu ne déclares pas le destructeur de la classe de base, un objet de type dérivé ne sera pas correctement détruit si tu invoque un comportement de destruction (delete :question:) alors que l'objet (de type dérivé) "passe pour être" un objet du type de base :P
Seule la virtualité du destructeur peut assurer un tel comportement ;)
Luc a tout à fait raison : il est parfois sage de rendre un destructeur protégé (pour que les classes enfants puissent y faire appel, car il faut bien détruire la "part de la calsse de base" :D ) et non virtuel.
Cela empêchera, bien sur, de détruire les objets de types dérivés lorsqu'ils "passeront pour etre" du type de base, mais peut, justement apporter une certaine sérénitécar cela force à ne détruire les objets que... quand on sait exactement ce que c'est ;)
Ah oui j'avais zappé la partie destructeur protected (et j'inverse souvent pour qui ne sera pas détruit si le destructeur n'est pas virtuel visiblement :aie: )
Donc ça dépends comment sont gérés les dits objets... si on fait des delete sur la classe abstraite le destructeur virtuel sera donc nécessaire.
Tout à fait :
Si la durée de vie des objets concrets est gérée de "manière commune" (grâce à une collection de pointeurs sur la classe de base), il faudra absolument que le destructeur soit public et virtuel.
Par contre, tu peux parfaitement envisager que la durée de vie de chaque type particulier soit géré séparément...
A ce moment là, tu sauras de toutes manière que chaque objet est d'un type dérivé clairement défini, et tu pourras sans problème le détruire sur base de son destructeur propre.
Celui de la classe de base peut donc parfaitement être protégé, et n'a aucun besoin d'être virtuel ;)
Salut,
La définition d'une fonction virtuelle pure dans une classe abstraite ne sera jamais exécutée par une résolution dynamique d'un appel. Elle nécessite forcément un appel explicite. Je n'ai pas souvenir d'un cas concret où cela avait un intérêt.
cf XI-B. Appel d'une fonction virtuelle pure
Je suis assez d'accord avec 3DArchi, au cas du destructeur près (là la définition du destructeur virtuelle pure est appelé automatiquement lors de la destruction des objets d'un sous-type), et à part cette utilisation du destructeur pour rendre une classe abstraite quand il n'y a pas d'autre fonctions qui pourraient l'être je ne vois pas d'autre cas d'utilisation.
Meyers en donne une dans Eff++ : donner à l'utilisateur de la classe la possibilité d'avoir un comportement par défaut à utiliser si il ne sait pas/n'a rien à mettre lors de la redéfinition dans une classe fille, cependant je ne suis pas convaincu de l'utilité pratique de cette utilisation, si on peut définir un comportement par défaut alors pourquoi ne pas mettre la fonction simplement virtuelle ? Pour rappeler à l'utilisateur que ce comportement par défaut est juste une porte de sortie et qu'il aurait du redéfinir cette fonction ? Je trouve que ca risque plus facilement d'embrouiller l'utilisateur de la classe qu'autre chose dans ce cas.
Tout dépend du comportement que tu fournis par défaut et de ce qui entoure la fonction virtuelle pure définie ...
Je m'explique : imaginons trente secondes que nous ayions effectivement une classe de base qui ait vocation à être abstraite, mais dont on a défini le destructeur de manière non virtuelle et protégée (parce que la durée de vie des objets concrets est gérée sous forme d'objets et non sous la forme de la classe dérivée).
Le destructeur n'a aucun intérêt à être virtuel, et il est donc impossible de le rendre virtuel pur!
Mais s'il est possible de fournir un comportement par défaut à l'ensemble des fonctions de la classe de base (y compris aux fonctions virtuelles), il faut bien trouver une fonction discriminante qui permette de rendre la classe abstraite!
Finalement, le fait de forcer l'utilisateur à redéfinir cette fonction n'est qu'un "effet secondaire" de la nécessité de rendre la classe de base abstraite ;)
Je suis d'accord que cela mérite d'être largement commenté (du genre : virtuelle pure pour garder la classe abstraite, mais si vous ne savez pas quoi faire pour la ré-implémenter, invoquez Base::laFonction, cela devrait aller ;) ), mais cela peut être la seule solution pour concilier l'inconciliable :D
Tu ne veux pas passer le destructeur en virtuelle pure (protégé) dans ce cas pour éviter que l'ajout d'un pointeur dans la vtable et une indirection lors de l'appel des destructeur ? Le gain apporté est significatif ?
Ne peut-on pas aussi mettre les constructeurs en protégé comme solution alternative ?
@flob : effectivement, mon précédent msg ne concernait pas le destructeur qui est un cas particulier.
@Koala : je ne suis pas d'accord avec toi. Je préfère un destructeur virtuel pur en protégé que rendre artificiellement une des fonctions virtuelles pures. Le fait d'avoir à l'appeler explicitement me dérange.
Bah, ce n'est peut etre pas forcément idéal, mais, par contre, cela a l'énorme avantage d'attirer l'attention de l'utilisateur sur le fait qu'il est sans doute préférable de réfléchir au comportement qui sera réellement adapté pour cette fonction.
C'est un peu comme dire à l'utilisateurLe message d'erreur deu compilateur aidant (impossible de créer une instance de la classe machin parce que la fonction faitCeci est virtuelle pure) l'utilisateur a réellement toutes les informations pour être en mesure de décider si, oui ou non, le comportement par défaut est adapté ;)Citation:
voilà, je fournis un comportement par défaut qui, au moins, ne fait pas d'erreur, mais qui n'est sans doute pas adapté à votre classe dérivée
Je suis d'accord que c'est tiré par les cheveux, que c'est sans doute un peu vicieux, mais ce n'est pas forcément dérangeant ;)
Oui, c'est ce que j'entendais par :
Mais comme tu l'as signalé, il faut quand même que ce soit clairement dit sinon l'utilisateur de la classe risque d'être perdue, et c'est ce qui me fait penser que ca ne doit pas être une pratique usuel : en général on essaie de rendre les choses user-friendly, pas d'utiliser une syntaxe valide mais peu utilisé qui demande à l'utilisateur d'adapter ses habitudes de programmation.Citation:
Pour rappeler à l'utilisateur que ce comportement par défaut est juste une porte de sortie et qu'il aurait du redéfinir cette fonction ?
Mais c'est peut etre le seul moyen de concilier l'inconciliable...
Le destructeur protégé (virtuel ou non) ne garanti pas la non instanciabilité de la classe de base : il ne fait que garantir la non destructabilité des objets lorsqu'ils sont considérés comme étant du type de base ;)
Même le fait de déclarer le reste du big four (opérateur d'affectation, constructeur par copie et constructeurs (prenant ou non des arguments) ) comme protégé n'empêche pas l'instanciation de la classe... Cela ne fait que rendre les choses un peu plus compliquées, mais il suffit d'une amitié mal gérée ou d'un
pour se retrouver avec une instance de la classe de base qui n'est d'aucun type dérivé :aie:Code:
1
2
3
4
5 class Derived : public Base { public: Base * truc(){return new Base;} };
La classe abstraite, et au travers d'elle, la fonction virtuelle pure, est, définitivement, la seule solution garantissant la non instanciabilité d'une (hiérarchie de) classe(s).
Si cette contrainte est vraiment forte (pour raison de spécifications), il peut devenir presque logique rendre une fonction virtuelle pure tout en lui donnant un comportement ;)
Si le destructeur est virtuelle pure et protégé ca garantie aussi les deux, pour ca que je ne comprends pas pourquoi ne pas utiliser cette solution ?
Non, si le big four est protégé, la seul solution (que je vois) c'est une fonction amie de la classe de base, c'est moins fort qu'une classe abstraite mais ca reste un risque assez maitrisé je trouve. (Du point de vue de l'utilisateur (*) la protection est la même, par contre du point de vue du développeur la classe abstraite te protège de créer involontairement une instance).
Pour reprendre l'idée de code que tu proposes :
Ne compile pas (à l'utilisation). :aie:Code:
1
2
3
4
5 template<class T> struct UseCtor : private T { static T* process() { return new T(); } };
Mais je suis d'accord avec toi, c'est la seule garantie totale de ne pouvoir créer un objet de ce type uniquement comme sous-objet :ccool: Mais si j'en ai besoin je peserais quand même le pour et le contre entre les différentes solutions.
(*) Sauf si tu laisses à l'utilisateur le soin d'implémenter les fonctions amies et membres de la classe de base ...
Non!!!
Il faudrait au minimum que le destructeur soit virtuel pur (quel que soit sa visibilité) pour qu'il puisse garantir quoi que ce soit au niveau de l'instanciabilité de la classe
Si le destructeur est protégé, tout ce qu'il peut garantir, c'est que les objets passant pour être du type de la classe de base ne pourront pas être détruit tant que l'on n'aura pas déterminé exactement quel est leur type dynamique ;)
Ou n'importe quelle classe dérivée, car les (fonctions) membres protégés sont accessibles à toutes les classes enfants (et à leur dérivées)Citation:
Non, si le big four est protégé, la seul solution (que je vois) c'est une fonction amie de la classe de base,
Disons que la classe abstraite est la seule solution garantie "100% idiot proof" permettant d'assurer la non instanciabilité d'une classe.Citation:
c'est moins fort qu'une classe abstraite mais ca reste un risque assez maitrisé je trouve. (Du point de vue de l'utilisateur (*) la protection est la même, par contre du point de vue du développeur la classe abstraite te protège de créer involontairement une instance).
Toute autre solution peut, certes, apporter un "certain niveau" de garantie, mais restera malgré tout contournable (la classe dérivée qui fait un new base en est un exemple, l'amitié mal gérée également ;) )
Sauf que tu changes les règles en partant sur un héritage privé, alors que j'ai chaque fois bien pris la précaution de préciser héritage public, c'est à dire une relation EST-UN, et non une relation EST IMPLEMENTEE EN TERME DE ;)Citation:
Pour reprendre l'idée de code que tu proposes :
Ne compile pas (à l'utilisation). :aie:Code:
1
2
3
4
5 template<class T> struct UseCtor : private T { static T* process() { return new T(); } };
Citation:
Mais je suis d'accord avec toi, c'est la seule garantie totale de ne pouvoir créer un objet de ce type uniquement comme sous-objet :ccool: Mais si j'en ai besoin je peserais quand même le pour et le contre entre les différentes solutions.
Non, cela va beaucoup plus loin, car toute classe dérivant publiquement de manière directe ou indirecte de la classe de base pour laquelle tu aurais déclaré le big four en protégé est susceptible de créer une instance de la classe de base.Citation:
(*) Sauf si tu laisses à l'utilisateur le soin d'implémenter les fonctions amies et membres de la classe de base ...
Or, tu peux, éventuellement, garder un oeil sur ce que toi tu fais, mais, si ta classe est destinée à être utilisée par d'autres, tu ne peux rien garantir à leur niveau :aie:
J'ai bien écrit virtuel pur (avec des fautes mais quand même) ...
Compile pas mieux (toujours à l'utilisation). :aie:Code:
1
2
3
4
5
6
7 template<class T> struct UseCtor : T { T* process() { return new T(); } };
Donnes moi un exemple (autre que les fonctions membres et amies de la classe de base), parce que je n'en vois aucun. :aie:Citation:
Non, cela va beaucoup plus loin, car toute classe dérivant publiquement de manière directe ou indirecte de la classe de base pour laquelle tu aurais déclaré le big four en protégé est susceptible de créer une instance de la classe de base.
C'est pas exactement ca l'accessiblité protégé. Tu peux accéder (au sein de la classe fille, scope et fonctions membres et amies) aux fonctions membres statiques héritées en accessibilité protégé, et accéder aux membres (fonctions ou non) héritées en accessibilité protégé sur des objets du type statique celui de la fille (et le mode d"héritage ne change rien à ca).Citation:
Ou n'importe quelle classe dérivée, car les (fonctions) membres protégés sont accessibles à toutes les classes enfants (et à leur dérivées)
Ca exclut les constructeurs de la manière dont tu veux l'utiliser (on peut y accéder pour construire le sous-objet par contre).
Qu'il est beau de voir se dérouler un pugilat intellectuel parce que j'ai mis mon grain de poivre... 8-)