OVH Cloud OVH Cloud

"function hiding" en C++

24 réponses
Avatar
Nicolas Castagne
Salutations,

je suis en train de réviser mes bases en C++ et... je ne comprends pas
pourquoi le "standard committee" a mis en place le mécanisme de
"function hiding" dans le langage.

Pourquoi diable si on écrit :
class Base {
void foo( int ) {}
};
class Derived: public Base {
void foo( char const [] ) {}
};
alors la méthode Base::foo(int) est cachée dans la classe fille Derived ?

Pourquoi faut il déclarer "using Base::foo ;" explicitement dans la
classe fille pour avoir de nouveau accès à Base::foo(int) ?


Ce n'est pas la première fois que je me pose la question, mais je n'ai
jamais trouvé de réponse satisfaisante, même après avoir posté sur
différents forums et newsgroup.
En tout cas, ce mécanisme me semble anti-intuitif...

Je serais très reconnaissant à quiconque pourrait me donner une bonne
raison / un bon exemple / un pointeur WWW !

Merci d'avance !
Nicolas

REFERENCES
C++ FAQ LITE / Marshall Cline :
http://www.parashift.com/c++-faq-lite/strange-inheritance.html#faq-23.9
La FAQ de Cline explique que le mécanisme existe, mais n'explique pas
pourquoi...

Le fil "reason for existance of function hiding" sur un Forum :
http://www.thescripts.com/forum/thread460595.html
Pas de 'bonne raison' donnée ici non plus.

Le fil "Why does "function hiding" exists in C++" des news comp.lang.c++.
Rien de clair là non plus.

10 réponses

1 2 3
Avatar
Jean-Marc Bourguet
Nicolas Castagne writes:

Salutations,

je suis en train de réviser mes bases en C++ et... je ne comprends pas
pourquoi le "standard committee" a mis en place le mécanisme de "function
hiding" dans le langage.

class Base {
void foo( int ) {}
};
class Derived: public Base {
void foo( char const [] ) {}
};
alors la méthode Base::foo(int) est cachée dans la classe fille Derived ?

Pourquoi faut il déclarer "using Base::foo ;" explicitement dans la classe
fille pour avoir de nouveau accès à Base::foo(int) ?


* Parce que les classes sont des "scopes" et que la classe
dérivée apparaît pour la recherche de nom exactement comme
un scope imbriqué dans la classe de base.

* Parce que la recherche de nom est définie de telle sorte
que quand on a trouvé un nom dans un scope, on ne cherche
pas dans les scopes englobant (ce qui n'a d'effet que pour
la surchage me semble-t'il). Pourquoi? Parce que si j'ai
bonne mémoire -- il y a un passage là-dessus dans D&E, mais
je n'ai pas le bouquin ici -- une première version des
règles de résolution de surchage aurait posé des problèmes
-- je ne sais plus lesquels -- qu'on a évité ainsi. Ensuite
les règles de résolution de surchage ont été modifiée, mais
on n'est pas revenu sur ce choix. Quand on s'en est rendu
compte, il y avait trop d'existant.

On a exactement la même comportement dans ce cas ci:

#include <iostream>
#include <ostream>

namespace a {
void f(int) {
std::cout << "a::f(int)n";
}
}

namespace b {
void f(double) {
std::cout << "b::f(double)n";
}
}

int main() {
using a::f;
{
using b::f;
f(1);
f(1.4);
}
}

Le deuxième f(1) utilise b::f parce que le f introduit dans
le bloc imbriqué cache le f introduit directement dans main
de même que le foo de Derived cache le foo de Base.

A+

--
Jean-Marc
FAQ de fclc++: http://www.cmla.ens-cachan.fr/~dosreis/C++/FAQ
C++ FAQ Lite en VF: http://www.ifrance.com/jlecomte/c++/c++-faq-lite/index.html
Site de usenet-fr: http://www.usenet-fr.news.eu.org

Avatar
Sylvain
Jean-Marc Bourguet wrote on 12/05/2006 15:27:

je suis en train de réviser mes bases en C++ et... je ne comprends pas
pourquoi le "standard committee" a mis en place le mécanisme de "function
hiding" dans le langage.

class Base {
void foo( int ) {}
};
class Derived: public Base {
void foo( char const [] ) {}
};
alors la méthode Base::foo(int) est cachée dans la classe fille Derived ?

Pourquoi faut il déclarer "using Base::foo ;" explicitement dans la classe
fille pour avoir de nouveau accès à Base::foo(int) ?


* Parce que les classes sont des "scopes" et que la classe
dérivée apparaît pour la recherche de nom exactement comme
un scope imbriqué dans la classe de base.


tu rappelles ici les limites des blocs de visibilité, mais pas leur
justification.

je considère également ce type de masquage comme "abusif" car les
fonctions ont des signatures différentes - qu'un compilo C K&R ne voit
qu'une fonction serait acceptable normal, pour un compilo C++ je
comprends moins.

les règles suivantes pourraient sembler suffisantes:

1- fonctions cachées mais accessibles:

class Base {
void foo( int ) {}
};
class Derived: public Base {
void foo( int ) {}
};

Derived d;
d.foo(1); // Derived::foo()
((Base&) d).foo(2); // Base::foo()

2- fonctions cachées et inaccessibles:

class Base {
virtual void foo( int ) {}
};
class Derived: public Base {
void foo( int ) {}
};

Derived d;
d.foo(1); // Derived::foo()
((Base&) d).foo(2); // Derived::foo()

3- fonctions (que l'on pourrait vouloir) non cachées car différentes

class Base {
void foo( int ) {}
};
class Derived: public Base {
void foo( char const [] ) {}
};

Derived d;
d.foo(1); // Base::foo()
d.foo("a"); // Derived::foo()


* Parce que la recherche de nom est définie de telle sorte
que quand on a trouvé un nom dans un scope, on ne cherche
pas dans les scopes englobant (ce qui n'a d'effet que pour
la surchage me semble-t'il).


la question serait alors: pourquoi le "function hiding" est basé sur les
noms et non sur les signatures de fonctions ?

Sylvain.


Avatar
kanze
Nicolas Castagne wrote:

je suis en train de réviser mes bases en C++ et... je ne
comprends pas pourquoi le "standard committee" a mis en place
le mécanisme de "function hiding" dans le langage.

class Base {
void foo( int ) {}
};
class Derived: public Base {
void foo( char const [] ) {}
};
alors la méthode Base::foo(int) est cachée dans la classe
fille Derived ?


La loi de la moindre surprise, sans doute.

Si les deux fonctions étaient publique, c'est discutable. Mais
personellement, je n'aimerais pas que la résolution du surcharge
et la récherche du nom dépend de l'accessibilité de la fonction.

Pourquoi faut il déclarer "using Base::foo ;" explicitement
dans la classe fille pour avoir de nouveau accès à

Base::foo(int) ?

Ce n'est pas la première fois que je me pose la question, mais
je n'ai jamais trouvé de réponse satisfaisante, même après
avoir posté sur différents forums et newsgroup.

En tout cas, ce mécanisme me semble anti-intuitif...


Voyons :

class Base
{
public:
// ...
private:
void f( char ) ; // c'est bien propre à
// l'implémentation,
// les utilisateurs n'ont pas à le
// connaître...
} ;

class Derived : public Base
{
public:
void doSomething()
{
// ...
f( 'x' ) ;
// ...
}

private:
void f( int ) ;
} ;

Que l'appelle de « f( 'x' ) » dans doSomething appelle Base::f,
ça serait une grande surprise pour l'auteur de Derived (qui en
principe, ne sait même pas qu'il y a un Base::f -- peut-être
qu'il n'y en avait même pas quand il a écrit Derived, et qu'elle
est apparue suite à une extention de Base qui ne le concernait
pas).

--
James Kanze GABI Software
Conseils en informatique orientée objet/
Beratung in objektorientierter Datenverarbeitung
9 place Sémard, 78210 St.-Cyr-l'École, France, +33 (0)1 30 23 00 34

Avatar
Sylvain
kanze wrote on 12/05/2006 16:12:

je suis en train de réviser mes bases en C++ et... je ne
comprends pas pourquoi le "standard committee" a mis en place
le mécanisme de "function hiding" dans le langage.

class Base {
void foo( int ) {}
};
class Derived: public Base {
void foo( char const [] ) {}
};
alors la méthode Base::foo(int) est cachée dans la classe
fille Derived ?


La loi de la moindre surprise, sans doute.

Si les deux fonctions étaient publique, c'est discutable. Mais
personellement, je n'aimerais pas que la résolution du surcharge
et la récherche du nom dépend de l'accessibilité de la fonction.


tu as raison sur le fait que rédigé comme tel les fonctions membres sont
privées, je pense que la question n'incluait pas ce point, les "class"
pouvant être lû comme des "struct" - sinon parler de masquage par scope
vs par surcharge vs par modifiers va être difficile.

tu as raison aussi sur le fait que l'accessibilité ne doit pas rendre
imprévisible la surcharge, mais également sur ce point, pourquoi la
"surcharge entre parent" serait plus compliqué (nécessite le "fonction
hiding") que la "surcharge locale" n'utilise (heureusement) pas ?

struct Base {
void foo( int ) {}
void foo( char* ) {}
};
les 2 sont visibles.

struct Derived: Base {
void foo( float ) {}
};
les 2 Base::foo() sont masquées.

Sylvain.


Avatar
Jean-Marc Bourguet
Sylvain writes:

je considère également ce type de masquage comme "abusif" car les fonctions
ont des signatures différentes - qu'un compilo C K&R ne voit qu'une
fonction serait acceptable normal
[...]

la question serait alors: pourquoi le "function hiding" est basé sur les
noms et non sur les signatures de fonctions ?


Dans ce que tu as coupé il y avait:

si j'ai bonne mémoire -- il y a un passage là-dessus dans
D&E, mais je n'ai pas le bouquin ici -- une première version
des règles de résolution de surchage aurait posé des
problèmes -- je ne sais plus lesquels -- qu'on a évité
ainsi. Ensuite les règles de résolution de surchage ont été
modifiée, mais on n'est pas revenu sur ce choix. Quand on
s'en est rendu compte, il y avait trop d'existant.



qui réponds plus ou moins à tes deux questions/objections
(du moins qui donne une spéculation basée sur mes souvenirs
de lecture de D&E).

A+

--
Jean-Marc
FAQ de fclc++: http://www.cmla.ens-cachan.fr/~dosreis/C++/FAQ
C++ FAQ Lite en VF: http://www.ifrance.com/jlecomte/c++/c++-faq-lite/index.html
Site de usenet-fr: http://www.usenet-fr.news.eu.org


Avatar
John Deuf
Nicolas Castagne :

[...]
Je serais très reconnaissant à quiconque pourrait me donner une bonne
raison / un bon exemple / un pointeur WWW !


"Design and Evolution of C++", Bjarne Stroustrup, page 77.

--
John Deuf

Avatar
Sylvain
Jean-Marc Bourguet wrote on 12/05/2006 20:56:

Dans ce que tu as coupé il y avait:

si j'ai bonne mémoire -- il y a un passage là-dessus dans
D&E, mais je n'ai pas le bouquin ici -- une première version
des règles de résolution de surchage aurait posé des
problèmes -- je ne sais plus lesquels -- qu'on a évité
ainsi. Ensuite les règles de résolution de surchage ont été
modifiée, mais on n'est pas revenu sur ce choix. Quand on
s'en est rendu compte, il y avait trop d'existant.



qui réponds plus ou moins à tes deux questions/objections
(du moins qui donne une spéculation basée sur mes souvenirs
de lecture de D&E).


merci Jean-Marc, j'avais bien lu en effet une spéculation. j'ai préféré
retenir les faits ou informations.
JD a confirmé cet ouvrage (sans l'ombre d'un ombre), donc merci pour
cette référence.

Sylvain.



Avatar
James Kanze
Sylvain wrote:
kanze wrote on 12/05/2006 16:12:


je suis en train de réviser mes bases en C++ et... je ne
comprends pas pourquoi le "standard committee" a mis en place
le mécanisme de "function hiding" dans le langage.




class Base {
void foo( int ) {}
};
class Derived: public Base {
void foo( char const [] ) {}
};
alors la méthode Base::foo(int) est cachée dans la classe
fille Derived ?




La loi de la moindre surprise, sans doute.



Si les deux fonctions étaient publique, c'est discutable.
Mais personellement, je n'aimerais pas que la résolution du
surcharge et la récherche du nom dépend de l'accessibilité de
la fonction.



tu as raison sur le fait que rédigé comme tel les fonctions
membres sont privées, je pense que la question n'incluait pas
ce point, les "class" pouvant être lû comme des "struct" -
sinon parler de masquage par scope vs par surcharge vs par
modifiers va être difficile.


Mon point reste. Pour les fonctions privées, toute autre
solution serait problamatique. Du coup, l'argument, c'est
l'orthogonalité -- et c'est un argument du poids, à mon avis,
parce qu'elle rend le langage plus facile à comprendre et à
enseigner.

Note que Java a d'autres règles, mais qu'en Java, il y a une
manque d'orthogonalité énorme -- private joue sur la visibilité
(et non l'accessabilité), à l'encontre de public ou protected ;
les règles qui concerne les variables sont différentes que
celles des fonctions, etc., etc.

tu as raison aussi sur le fait que l'accessibilité ne doit pas
rendre imprévisible la surcharge, mais également sur ce point,
pourquoi la "surcharge entre parent" serait plus compliqué
(nécessite le "fonction hiding") que la "surcharge locale"
n'utilise (heureusement) pas ?


struct Base {
void foo( int ) {}
void foo( char* ) {}
};
les 2 sont visibles.


struct Derived: Base {
void foo( float ) {}
};
les 2 Base::foo() sont masquées.


Je ne comprends pas trop ton point ici. Dans Base, les fonctions
membres de Base sont visibles. Dans Derived, les fonctions
membres de Derived. Ça me semble extrèmement logique et
orthogonal.

Imagine plutôt quelque chose du genre :

class Base
{
void toto( int ) ;
public:
void toto( char* ) ;
} ;

class Derived : public Base
{
public:
void toto( double ) ;
} ;

Est-ce que ça te semblerais cohérent que dans Derived, une des
toto de Base soit visible, et l'autre non ? (C'est effectivement
le cas en Java, par exemple. C'est un autre choix. Et je ne
cherche pas à convaincre que le choix de C++ est le seul
possible, ni même forcément le meilleur -- bien que je le
préfère personnellement. Seulement que c'est un choix
raisonable.)

--
James Kanze
Conseils en informatique orientée objet/
Beratung in objektorientierter Datenverarbeitung
9 place Sémard, 78210 St.-Cyr-l'École, France +33 (0)1 30 23 00 34



Avatar
Sylvain
James Kanze wrote on 13/05/2006 11:47:

Mon point reste. Pour les fonctions privées, toute autre
solution serait problamatique. Du coup, l'argument, c'est
l'orthogonalité -- et c'est un argument du poids, à mon avis,


c'est à dire ? faut-il comprendre que le compilo va avoir suffisamment
de mal à remonter l'arborescense de classes lors de la recherche d'une
méthode à signature (prototype) donnée, qu'il sera vraiment en peine de
traiter, en plus, un modifier 'public' ou 'private' ??

btw, si tu peux expliciter 'orthogonalité' dans le cadre des règles
d'accès, je ne suis pas sur de partager le même sens.

parce qu'elle rend le langage plus facile à comprendre et à
enseigner.


la discussion valait hors revue de code ;)

Note que Java a d'autres règles, mais qu'en Java, il y a une
manque d'orthogonalité énorme -- private joue sur la visibilité
(et non l'accessabilité), à l'encontre de public ou protected ;
les règles qui concerne les variables sont différentes que
celles des fonctions, etc., etc.


comprends pas ! private, protected, package ou public sont gérés avec
les mêmes règles, et ces règles s'appliquent à l'identique à une donnée
ou une fonction membre.

ce qui change entre Java et C++, c'est ce que l'on peut appeler
'visibilité' qui contraint plus fortement ce qui est accessible depuis
l'endroit où en se trouve (ce que l'on voit). ainsi ton exemple (autre
thread)
public base clone() {
return doClone();
}
private abstract base doClone();

ne pourrait pas fonctionner car dans le contexte de "clone" les seules
méthodes privées accessibles sont celles de base.

struct Base {
void foo( int ) {}
void foo( char* ) {}
};
les 2 sont visibles.

struct Derived: Base {
void foo( float ) {}
};
les 2 Base::foo() sont masquées.


Je ne comprends pas trop ton point ici.


mon point est:
Derived d;
d.foo(1); error (plutot unexpected, le int sera casté en float)
d.foo("a"); error

Dans Base, les fonctions membres de Base sont visibles.
Dans Derived, les fonctions membres de Derived.
Ça me semble extrèmement logique et orthogonal.


le point est, depuis le début, comment invoquer (depuis l'extérieur) les
services de la classe (pas comment cette classe implémente sa toutouille
interne).

Imagine plutôt quelque chose du genre :

class Base {
void toto( int ) ;
public:
void toto( char* ) ;
} ;
class Derived : public Base {
public:
void toto( double ) ;
} ;

Est-ce que ça te semblerais cohérent que dans Derived, une des
toto de Base soit visible, et l'autre non ?


me semblerait-il logique qu'une fonction publique (toto(char*)) dont
Derived hérite publiquement soit visible (spontanément publique sans
using) ? ben oui sans hésitation.

qu'il ne soit pas possible d'accéder (via une instance de Derived) à
toto(int) me paraitra également tout à fait normal puisqu'elle est privée.

au delà de ces "paraître" ce qui est formulé ici est la seule lecture
des attributs d'accès définis par ces classes - une lecture simple et
directe qu'il aurait été facile de comprendre et d'enseigner.

(C'est effectivement le cas en Java, par exemple.


en effet.

Sylvain.


Avatar
kanze
Sylvain wrote:
James Kanze wrote on 13/05/2006 11:47:

Mon point reste. Pour les fonctions privées, toute autre
solution serait problamatique. Du coup, l'argument, c'est
l'orthogonalité -- et c'est un argument du poids, à mon
avis,


c'est à dire ? faut-il comprendre que le compilo va avoir
suffisamment de mal à remonter l'arborescense de classes lors
de la recherche d'une méthode à signature (prototype) donnée,
qu'il sera vraiment en peine de traiter, en plus, un modifier
'public' ou 'private' ??


Le compilateur, non. Mais la règle est plus simple à comprendre
s'il n'y a pas de différence entre la gestion des public et des
private.

btw, si tu peux expliciter 'orthogonalité' dans le cadre des
règles d'accès, je ne suis pas sur de partager le même sens.


C-à-d qu'en C++, les règles d'accès sont exactement ça -- elles
ne règlent que les droits d'accès, non la visibilité. En Java,
en revanche, « private » ne signifie pas inaccessible, mais
carrément invisible. Ce qui n'est pas le cas de protected, si je
ne me trompe pas.

Note que Java a d'autres règles, mais qu'en Java, il y a une
manque d'orthogonalité énorme -- private joue sur la
visibilité (et non l'accessabilité), à l'encontre de public
ou protected ; les règles qui concerne les variables sont
différentes que celles des fonctions, etc., etc.


comprends pas ! private, protected, package ou public sont
gérés avec les mêmes règles, et ces règles s'appliquent à
l'identique à une donnée ou une fonction membre.


En partie -- ce qu'ils appellent les règles d'accès contrôle en
partie la visibilité. Reste qu'il y a des incohérences : une
variable dans une classe dérivée cache une variable du même nom
dans une classe de base, tandis qu'une fonction non.

ce qui change entre Java et C++, c'est ce que l'on peut
appeler 'visibilité' qui contraint plus fortement ce qui est
accessible depuis l'endroit où en se trouve (ce que l'on
voit). ainsi ton exemple (autre thread)
public base clone() {
return doClone();
}
private abstract base doClone();

ne pourrait pas fonctionner car dans le contexte de "clone"
les seules méthodes privées accessibles sont celles de base.


Tout à fait. Quand j'ai fait du Java, on s'est servi des
fonctions protected pour le faire.

struct Base {
void foo( int ) {}
void foo( char* ) {}
};
les 2 sont visibles.

struct Derived: Base {
void foo( float ) {}
};
les 2 Base::foo() sont masquées.


Je ne comprends pas trop ton point ici.


mon point est:
Derived d;
d.foo(1); error (plutot unexpected, le int sera casté en float)
d.foo("a"); error


OK. C'est une conséquence de la règle générale. En revanche, tu
serait bien d'accord avec moi que :

class Base
{
private :
void foo( int ) ;
public :
void foo( double ) ;
} ;

class Derived : public Base
{
public:
void foo( double ) ;
} ;

Derived d ;
d.foo( 5 ) ;

si la dernière ligne donnait une erreur, ça serait étonnante.

D'autres règles sont possibles. En C++, la règle de base, c'est
que les contrôles d'accès ne concernent que l'accès, non la
visibilité. Étant donné ceci, la règle en question est plutôt
logique.

Des deux variantes ont des avantages et des inconvenients. Si je
concevais un langage aujourd'hui, je crois que je commencerais
par les règles du C++, mais j'ajouterais une catégorie
supplémentaire : « hidden », qui empècherait réelement la
visibilité. Ça manquerait de cohérence, mais ça serait peut-être
plus pratique, en offrant les avantages des deux points de vues.

Dans Base, les fonctions membres de Base sont visibles.
Dans Derived, les fonctions membres de Derived.
Ça me semble extrèmement logique et orthogonal.


le point est, depuis le début, comment invoquer (depuis
l'extérieur) les services de la classe (pas comment cette
classe implémente sa toutouille interne).

Imagine plutôt quelque chose du genre :

class Base {
void toto( int ) ;
public:
void toto( char* ) ;
} ;
class Derived : public Base {
public:
void toto( double ) ;
} ;

Est-ce que ça te semblerais cohérent que dans Derived, une
des toto de Base soit visible, et l'autre non ?


me semblerait-il logique qu'une fonction publique
(toto(char*)) dont Derived hérite publiquement soit visible
(spontanément publique sans using) ? ben oui sans hésitation.


Ma question portait plutôt sur la visibilité de la fonction
privée, et la cohérence des deux.

qu'il ne soit pas possible d'accéder (via une instance de
Derived) à toto(int) me paraitra également tout à fait normal
puisqu'elle est privée.

au delà de ces "paraître" ce qui est formulé ici est la seule
lecture des attributs d'accès définis par ces classes - une
lecture simple et directe qu'il aurait été facile de
comprendre et d'enseigner.


Je ne trouve rien de compliqué avec les règles C++. Telles
qu'elles étaient au départ, en tout cas : on a une liste
ordonnée d'endroits à chercher, et on s'arrête de chercher dès
qu'on a trouvé. Les problèmes viennent d'ailleurs : les
opérateurs surchargés, par exemple (où la présence d'un
opérateur membre n'empèche pas de considérere aussi les
opérateurs globaux), ou la récherche dépendante des noms --
aller expliquer à un débuttant pourquoi si je définis un
operator<<( std::ostream&, std::vector<int> const&), le
compilateur le trouve quand je fais "std::cout << v" moi-même,
mais ne le rétrouve pas quand c'est std::ostream_iterator qui
veut s'en servir.

(C'est effectivement le cas en Java, par exemple.


en effet.


À vrai dire, la plus grande confusion en Java vient de ce qu'ils
ont adopté le vocabulaire du C++ pour quelque chose qui en fin
de compte n'a rien à voir. La spécification Java parle
constamment des contrôles d'accès quand il s'agit en fait des
contrôles de visibilité. Selon le cas, l'un ou l'autre est plus
utile -- il m'a manqué la possibilité de faire une fonction
virtuelle privée en Java, par exemple, mais il y a aussi bien
des fois que j'aimerais définir des fonctions ne C++ qui ne sont
pas du tout visible en dehors de la classe.

--
James Kanze GABI Software
Conseils en informatique orientée objet/
Beratung in objektorientierter Datenverarbeitung
9 place Sémard, 78210 St.-Cyr-l'École, France, +33 (0)1 30 23 00 34



1 2 3