ProgrammationDéveloppeur système / infrastructure C++

Parlez-nous de l'ordre de recherche des fonctions et des variables lors de l'héritage (name lookup, scope), ainsi que des complexités qui surviennent avec l'ambiguïté des noms dans les hiérarchies (surtout en cas d'héritage virtuel). Comment résoudre de tels conflits ?

Réussissez les entretiens avec l'assistant IA Hintsage

Réponse

En C++, l'ordre de recherche des noms est régi par des règles de résolution des noms (name lookup), qui prennent en compte les zones de visibilité (scope), la dissimulation (hiding) et les spécificités de l'héritage. Points clés :

  • Lors de l'appel d'une méthode / accès à une variable, la recherche se fait de la zone dérivée à la zone de base.
  • En cas d'héritage multiple, si un nom apparaît dans les deux branches de base, une ambiguïté (ambiguity) se produit.
  • L'héritage virtuel aide à éviter la duplication (diamond problem), mais nécessite également de gérer explicitement les noms.

Résolution des conflits :

  • Pour les fonctions masquées dans la classe dérivée, on peut utiliser using :
class Base { public: void foo(int) {} }; class Derived : public Base { public: using Base::foo; void foo(double) {} // Version surchargée };
  • Pour les variables ou fonctions avec des noms identiques :
    • Il est nécessaire d'indiquer explicitement le nom de la classe de base : Base::x ou Base::foo().

Exemple d'ambiguïté en héritage diamond :

struct A { int x; }; struct B : virtual A {}; struct C : virtual A {}; struct D : B, C { void test() { x = 42; } // OK : x est unique, grâce au virtuel };

Question piège

Si une méthode foo(int) est déclarée dans la classe de base, et foo(double) dans la classe dérivée, foo(int) est-il accessible depuis l'objet de la classe dérivée ?

Réponse : Non, la méthode foo(double) "masque" toutes les versions surchargées de foo dans la classe de base. Pour accéder à foo(int), il faut écrire explicitement using Base::foo ou Base::foo dans l'appel.

Exemple :

class Base { public: void foo(int) { } }; class Derived : public Base { public: void foo(double) {} }; Derived d; d.foo(3); // Erreur ! foo(int) n'est pas visible

Utilisons :

class Derived : public Base { public: using Base::foo; void foo(double) {} }; d.foo(3); // Tout fonctionne

Exemples d'erreurs réelles dues à l'ignorance des subtilités du sujet


Histoire

Dans un projet avec héritage multiple, une classe a été héritée de deux bases avec des méthodes foo() identiques. Les auteurs n'ont pas utilisé l'héritage virtuel, ce qui a créé deux copies de la classe de base — tous les appels à foo() devenaient ambigus, le compilateur refusait de compiler le code. Cela a été résolu par l'utilisation de l'héritage virtuel et l'indication explicite de Base::foo() lors de l'appel.


Histoire

Dans un moteur graphique, une classe dérivée définissait son propre draw(), sans faire using pour draw() de la classe de base. Lors du refactoring, la mauvaise version de draw() était appelée — certaines parties de l'interface ne se dessinaient plus. L'erreur a été trouvée seulement après un examen approfondi de la structure des classes.


Histoire

Lors du passage à un nouveau compilateur, la compilation est devenue impossible en raison de l'ambiguïté des noms lors de l'héritage multiple et virtuel. Une implémentation auparavant fonctionnelle ne prenait pas en compte les différentes chaînes d'inclusion, conduisant à des ensembles de noms différents dans l'unité de traduction.