nom caché et problème de base fragile

Je l’ai vu dire que C ++ cache son nom dans le but de réduire le problème de la classe de base fragile. Cependant, je ne vois vraiment pas en quoi cela pourrait aider. Si la classe de base introduit une fonction ou une surcharge qui n’existait pas auparavant, cela pourrait entrer en conflit avec celles introduites par la classe dérivée, ou des appels non qualifiés à des fonctions globales ou des fonctions membres – mais je ne vois pas en quoi cela est différent pour les surcharges. . Pourquoi les surcharges de fonctions virtuelles devraient-elles être traitées différemment de toutes les autres fonctions?

Edit: Laissez-moi vous montrer un peu plus de quoi je parle.

struct base { virtual void foo(); virtual void foo(int); virtual void bar(); virtual ~base(); }; struct derived : base { virtual void foo(); }; int main() { derived d; d.foo(1); // Error- foo(int) is hidden d.bar(); // Fine- calls base::bar() } 

Ici, foo(int) est traité différemment de bar() , car c’est une surcharge.

Je suppose que par “classe de base fragile”, vous entendez une situation dans laquelle des modifications de la classe de base peuvent rompre le code utilisant des classes dérivées (c’est-à-dire la définition que j’ai trouvée sur Wikipedia). Je ne sais pas ce que les fonctions virtuelles ont à voir avec cela, mais je peux expliquer comment le fait de cacher permet d’éviter ce problème. Considérer ce qui suit:

 struct A {}; struct B : public A { void f(float); }; void do_stuff() { B b; bf(3); } 

L’appel de fonction dans do_stuff appelle B::f(float) .

Supposons maintenant que quelqu’un modifie la classe de base et ajoute une fonction void f(int); . Sans se cacher, cela correspondrait mieux à l’argument de la fonction dans main ; vous avez changé le comportement de do_stuff (si la nouvelle fonction est publique) ou provoqué une erreur de compilation (si elle est privée), sans modifier do_stuff ni aucune de ses dépendances directes. En masquant, vous n’avez pas changé le comportement, et une telle rupture n’est possible que si vous désactivez explicitement le masquage avec une déclaration using .

Je ne pense pas que les surcharges de fonctions virtuelles soient traitées différemment des surcharges de fonctions habituelles. Il pourrait cependant y avoir un effet secondaire.

Supposons que nous ayons une hiérarchie en 3 couches:

 struct Base {}; struct Derived: Base { void foo(int i); }; struct Top: Derived { void foo(int i); }; // hides Derived::foo 

Quand j’écris:

 void bar(Derived& d) { d.foo(3); } 

l’appel est résolu de manière statique en Derived::foo , quel que soit le type vrai (exécution) que d peut avoir.

Cependant, si je présente alors virtual void foo(int i); dans la Base , alors tout change. Soudain Derived::foo et Top::foo deviennent des substitutions, au lieu de simples surcharges qui masquaient le nom dans leur classe de base respective.

Cela signifie que d.foo(3); est maintenant résolu de manière statique non pas directement à un appel de méthode, mais à une répartition virtuelle.

Donc Top top; bar(top) Top top; bar(top) appellera Top::foo (via virtual dispatch), où il s’appelait précédemment Derived::foo .

Ce n’est peut-être pas souhaitable. Il pourrait être corrigé en qualifiant explicitement l’appel d.Derived::foo(3); , mais c’est un effet secondaire malheureux.

Bien sûr, il s’agit avant tout d’un problème de conception. Cela ne se produira que si la signature est compatible, sinon nous cacherons le nom et aucun remplacement; par conséquent, on pourrait affirmer que le fait de remplacer des fonctions “potentielles” pour des fonctions non virtuelles est toujours une source de problèmes (si vous avez un avertissement, vous pouvez le justifier, pour éviter de vous mettre dans une telle situation).

Remarque: si nous supprimons Top, il est parfaitement correct d’introduire la nouvelle méthode virtuelle, étant donné que tous les anciens appels ont déjà été traités par Derived :: foo, de sorte que seul le nouveau code risque d’être affecté.

Il convient toutefois de garder à l’esprit lors de l’introduction de nouvelles méthodes virtual dans une classe de base, en particulier lorsque le code impacté est inconnu (bibliothèques livrées aux clients).

Notez que C ++ 0x a l’atsortingbut override pour vérifier qu’une méthode est réellement un remplacement d’un virtuel de base; Bien que cela ne résolve pas le problème immédiat, on pourrait imaginer à l’avenir que les compilateurs soient avertis des remplacements “accidentels” (c’est-à-dire des remplacements non marqués en tant que tels), auquel cas un tel problème pourrait être résolu au moment de la compilation après l’introduction de la méthode virtuelle.

Dans Conception et évolution du C ++ , Bjarne Stroustrup Addison-Weslay, 1994, section 3.5.3, pages 77 et 78, BS explique que la règle selon laquelle un nom dans une classe dérivée masque toute définition du même nom dans ses classes de base est ancienne. et date de C avec Classes. Lors de son introduction, BS l’a considérée comme la conséquence évidente des règles de scope (il en va de même pour les blocs de code nesteds ou les espaces de noms nesteds, même si les espaces de noms ont été introduits après). L’opportunité de ses interactions avec les règles de surcharge (le jeu surchargé ne contient pas la fonction définie dans les classes de base, ni dans les blocs englobants – maintenant sans danger étant donné que la déclaration de fonctions en bloc est ancienne -, ni dans les espaces de noms enfermés occasionnellement grèves également) a été débattu, au point que G ++ a implémenté des règles alternatives autorisant la surcharge, et BS a fait valoir que la règle actuelle permettait de prévenir les erreurs dans des situations du type (inspirées de vrais problèmes réels avec g ++)

 class X { int x; public: virtual void copy(X* p) { x = p->x; } }; class XX: public X { int xx; public: virtual void copy(XX* p) { xx = p->xx; X::copy(p); } }; void f(X a, XX b) { a.copy(&b); // ok: copy X part of b b.copy(&a); // error: copy(X*) is hidden by copy(XX*) } 

Ensuite, BS continue

Rétrospectivement, je soupçonne que les règles de surcharge introduites dans la version 2.0 auraient pu traiter cette affaire. Considérez l’appel b.copy(&a) . La variable b est une correspondance de type exacte pour l’argument implicite de XX::copy , mais nécessite une conversion standard pour correspondre à X::copy . La variable a , d’autre part, correspond exactement à l’argument explicite de X::copy , mais nécessite une conversion standard correspondant à XX:copy . Ainsi, si la surcharge avait été autorisée, l’appel aurait été une erreur car il est ambigu.

Mais je ne vois pas où est l’ambiguïté. Il me semble que BS a négligé le fait que &a ne peut pas être implicitement converti en XX* et que seul X::copy a donc été pris en compte.

En effet essayer avec des fonctions libres (amis)

 void copy(X* t, X* p) { t->x = p->x; } void copy(XX* t, XX* p) { t-xx = p->xx; copy((X*)t, (X*)p); } 

Je ne reçois aucune erreur d’ambiguïté avec les compilateurs actuels et je ne vois pas en quoi les règles du manuel de référence C ++ annoté feraient une différence.