web-dev-qa-db-fra.com

Fonction avec le même nom mais une signature différente dans la classe dérivée

J'ai une fonction avec le même nom, mais avec une signature différente dans une base et des classes dérivées. Lorsque j'essaie d'utiliser la fonction de la classe de base dans une autre classe qui hérite de la dérivée, je reçois une erreur. Voir le code suivant:

class A
{
    public:
    void foo(string s){};
};

class B : public A
{
    public:
    int foo(int i){};
};

class C : public B
{
    public:
    void bar()
    {
        string s;
        foo(s);
    }
};

Je reçois l'erreur suivante du compilateur gcc:

In member function `void C::bar()': no matching function for call to `C::foo(std::string&)' candidates are: int B::foo(int)

Si je supprime int foo(int i){}; de la classe B, ou si je le renomme de foo1, Tout fonctionne correctement.

Quel est le problème avec ça?

83
Igor Oks

Les fonctions dans les classes dérivées qui ne remplacent pas les fonctions dans les classes de base mais qui ont le même nom cacher d'autres fonctions du même nom dans la classe de base.

Il est généralement considéré comme une mauvaise pratique d'avoir des fonctions dans les classes dérivées qui ont le même nom que les fonctions de la classe de basse qui ne sont pas destinées à remplacer les fonctions de la classe de base car ce que vous voyez n'est généralement pas un comportement souhaitable. Il est généralement préférable de donner des noms différents à différentes fonctions.

Si vous devez appeler la fonction de base, vous devrez étendre l'appel en utilisant A::foo(s). Notez que cela désactiverait également tout mécanisme de fonction virtuelle pour A::foo(string) en même temps.

72
CB Bailey

C'est parce que la recherche de nom s'arrête si elle trouve un nom dans l'une de vos bases. Il ne regardera pas au-delà dans d'autres bases. La fonction dans B ombres la fonction dans A. Vous devez re-déclarer la fonction de A dans le cadre de B, de sorte que les deux fonctions soient visibles de l'intérieur de B et C:

class A
{
    public:
    void foo(string s){};
};

class B : public A
{
    public:
    int foo(int i){};
    using A::foo;
};

class C : public B
{
    public:
    void bar()
    {
        string s;
        foo(s);
    }
};

Edit: La vraie description du Standard est (à partir de 10.2/2):

Les étapes suivantes définissent le résultat de la recherche de nom dans une étendue de classe, C. Premièrement, chaque déclaration pour le nom dans la classe et dans chacun de ses sous-objets de classe de base est prise en compte. Un nom de membre f dans un sous-objet B cache un nom de membre f dans un sous-objet A si A est un sous-objet de classe de base de B. Toutes les déclarations qui sont ainsi masquées sont éliminées de la considération. Chacune de ces déclarations qui a été introduite par une déclaration d'utilisation est considérée comme provenant de chaque sous-objet de C qui est du type contenant la déclaration désignée par la déclaration d'utilisation.96) Si l'ensemble de déclarations résultant n'est pas tous des sous-objets du même type, ou l'ensemble a un membre non statique et comprend des membres de sous-objets distincts, il y a une ambiguïté et le programme est mal formé. Sinon, cet ensemble est le résultat de la recherche.

Il a ce qui suit à dire dans un autre endroit (juste au-dessus):

Pour une id-expression [quelque chose comme "foo"], la recherche de nom commence dans la portée de classe de this; pour un id qualifié [quelque chose comme "A :: foo", A est un spécificateur de nom imbriqué], la recherche de nom commence dans la portée du spécificateur de nom imbriqué. La recherche de nom a lieu avant le contrôle d'accès (3.4, clause 11).

([...] mis par moi). Notez que cela signifie que même si votre foo en B est privé, le foo en A ne sera toujours pas trouvé (car le contrôle d'accès a lieu plus tard).

103