|

Écrivez
à AILES ! |

Retour
vers programmation |

Retour
vers les questions C++ |

Retour
vers les questions sur
l'héritage privé |
|
|
Questionnaire C++
Héritage privé
[AnnotatedC++], pp.
242 et suiv.
|
Finalement, l'héritage privé peut
s'avérer très efficace lorsqu'il s'agit de
"masquer" les caractéristiques des
classes de base. Cela se rencontre surtout
lorsque l'on développe une nouvelle couche à
partir d'une librairie existante dont on ne
souhaite pas trop divulguer les services...
Cela sert également lorsque l'on redéfini un
service bien précis d'un objet, et que l'on veut
à tout prix éviter qu'un programmeur imprudent
utilise, par un upcast "mal placé", le
service de la classe de base.
Alors... qu'est-ce que l'héritage privé ? Et
c'est quoi "upcast" ?
Voici toutes les réponses! |
A
quoi sert l'héritage privé ?
Bon, ok. La réponse évidente
consiste à dire :
« Une classe qui hérite
de façon privée d'une autre rend les méthodes
publiques et protégées de sa classe mère (classe de
Base "B") en méthodes privées de la classe
fille (classe Dérivée "D").
Cela permet de masquer l'interface de la classe mère à
tous les utilisateurs de la classe fille
».
ouf.
Mais qu'est qui permet d'être sûr qu'un utilisateur de
la classe fille n'utilisera pas les méthodes de la
classe mère ?
l'upcast est
impossible! En clair, si l'on a :
class B {} ;
class D : private B {} ;
Faire :
D* p_d ;
B* p_b = (B*)p_d ; est
impossible.
(upcast = caster une instance d'une classe fille
en celle de la classe mère)
L'upcast peut être :
- explicite comme ci-dessus ou,
- implicite, dans le cas, par exemple,
d'une liste de B* ou encore dans le cas où l'on accède,
depuis une instance de de D, à une fonction f_b() de B :
p_d->f_b() ; revient en fait à faire
l'upcast implicite ((B*)p_d)->f_b()
; ce qui sera interdit par le compilateur dans
le cas où D dérive de B de façon "private"!
Si l'on a bien cela à l'esprit, les réponses aux autres
questions coulent d'elles-même.
Access specifier for a base class :
class B { /* ... */ } ;
class
D1 : B { /* ... */ } ; // héritage private
(par défaut) de B
struct
D2 : B { /* ... */ } ; // héritage public
(par défaut) de B
class
D3 : public A, B, C { /* ... */ } ; // héritage public
(comme spécifié) de A, mais private
(par défaut) de B et de C
Use
of a static member of a base class
class B {
public:
static void f() ;
void g() ;
};
class D : private B
{
B* mem() { return this ; } // ok! l'upcast
implicite de D en une classe mère directe
privée est autorisée pour les fonctions membre de D
(classe fille directe
de B) ainsi que pour les fonctions friend de D.
} ;
class DD : public D
{
void h() ;
};
void
DD::h()
{
B::f() ; // ok! Cf explication
ci-dessous (*)
D::f() ; // pas ok : on ne peut
pas convertir 'this' en B* (upcast interdit)
this->f() ; // pas ok : on ne peut
pas convertir 'this' en B* (upcast interdit)
this->B::f() ; // pas ok : on ne peut
pas convertir 'this' en B* (upcast interdit)
f() ; // pas ok : on ne peut
pas convertir 'this' en B* (upcast interdit)
g() ; // pas ok : on ne peut
pas convertir 'this' en B* (upcast interdit)
g() ; // pas ok : on ne peut
pas convertir 'this' en B* (upcast interdit)
}
(*) Mais
pourquoi donc B::f() marche-t-il ??? Cela n'implique-t-il
pas de transformer 'this' en B* (upcast?!) ?
En fait non : l'héritage privé n'affecte pas
les membres statiques de la classe de base, à condition
d'y accéder directement (sans upcast).
B::f() remplit cette condition alors que D::f() revient
à écrire ((B*)this)->:f()...
De plus, imaginons que B::f() soit interdit...
Alors, comment expliquer que la fonction globale glob() :
void glob()
{
B::f() ; // ok!
}
puisse fonctionner sans problème ?
En fait, B::f() ne requiert aucun (up)cast -
implicite ou explicite.
En revanche, D::f() en requiert un, ne serait-ce que pour
savoir d'où vient cette fonction 'f'...
Access Declaration
class B {
public:
int a ;
void f() ;
void g() ;
private:
int b ;
void g( int ) ;
protected:
int c ;
};
class
D : private B
{
public:
B::a ; // a redevient un membre public de D
B::b ; // *error* : b était
privé dans B, il ne peut devenir public dans D
(sinon, ce serait un moyen de violer l'encapsulation des
données voulues dans B...)
void f( int ) ;
B::f ; // *error* l'accès de B::f() ne peut
être ajusté car il existe un D::f() de signature
différente, mais de même nom!
B::g ; // *error* les fonctions B::g() (au
nombre de 2) ne peuvent être rendues *toutes les deux*
public car leurs droits d'accès diffèrent. Cela
marcherait si elles étaient toutes deux public dans B.
protected:
B::c ; // c redevient un
membre protected de D
B::a ; // *error* a était
public sous B, il ne peut devenir protected ou private
sous D...
}
;
On comprend bien pourquoi, par héritage, on ne peut
augmenter les droits d'accès des variables membre de la
classe de base qui se retrouvent dans la classe fille.
(Violation de l'encapsulation, comme dans l'exemple de
B::b).
Il est moins évident de comprendre pourquoi on ne peut
les restreindre :
Si l'on imagine une classe DD héritant
publiquement de B, n'est-il pas légitime de se
dire « Je ne veux pas que les utilisateur de DD
utilisent 'a' (issu de B). Je rend donc a protected ou
private dans DD! »
class DD : public B
{
private:
B::a; // ??? *error*
};
Oui mais : n'importe quel petit malin, bien décidé à
utiliser quand même B::a n'a qu'à faire un upcast
:
DD* p_dd ;
B* p_monB = (B*)p_dd ;
Ils peuvent ainsi accéder à B::a alors même que
celui-ci avait explicitement été déclaré private dans
DD...
Or cette même "astuce" ne fonctionnerait pas
dans le cas d'un héritage privé (cas de la classe D),
puisque l'upcast est impossible...
Alors, pourquoi l'autoriserait-on dans un cas et pas dans
l'autre ?...
=> logiquement, toute restriction d'accès ou
augmentation d'accès d'une variable ou fonction membre
d'une classe de base est interdite dans sa classe fille
(que ce soit pour l'héritage public, protected ou private)...
Seule la restitution dans
une classe fille des droits d'accès issus de la classe
mère est autorisée.
Cette "règle" explique d'ailleurs toutes les
autres réponses à cette question.
|