2010-11-11 5 views
7

Tenir compte:La substitution d'une méthode virtuelle non constante cache-t-elle une surcharge const?

#include <iostream> 

using namespace std; 

struct A { 
    virtual void f() { cout << "A::f" << endl; } 
    virtual void f() const { cout << "A::f const" << endl; } 
}; 

struct B : public A {}; 

struct C : public A { 
    virtual void f() { cout << "C::f" << endl; } 
}; 


int main() 
{ 
    const B b; 
    b.f(); // prints "A::f const" 

    const C c; 
    c.f(); 
    // Compile-time error: passing ‘const C’ as ‘this’ argument of 
    // ‘virtual void C::f()’ discards qualifiers 
} 

(. J'utilise GCC)

Il semble donc que la version const de f() qui est caché en C. Cela me semble beaucoup de sens, mais est il mandaté par la norme?

+4

"Virtuel" est un hareng rouge. Nous n'appelons aucun 'f' virtuellement (via un pointeur ou une référence de classe de base) ici. Toutes les recherches de 'f' trouvent le' f' le plus dérivé. – MSalters

+0

Virtual et const ne s'appliquent pas vraiment à la question, mais je les ai laissés comme tags car je ne vois pas beaucoup de mal et je n'ai pas besoin d'inclure un tag plus pertinent. –

+2

Je suis d'accord sur 'virtual', mais' const' est la raison d'être de toute cette question. Remplacer 'f()' cache 'f() const'. – Ari

Répondre

4

Je relieront (une fois de plus) ce grand article:

D'abord, [le compilateur] regarde dans le champ d'application immédiate , dans ce cas, le champ de classe C, et fait une liste des toutes les fonctions qu'il peut trouver qui sont nommé f (indépendamment du fait qu'ils sont accessible ou même prendre le bon nombre de paramètres ). seulement si elle ne continue-t-il alors « vers l'extérieur » dans la prochaine enfermant champ [...]

Alors oui, la version const de f est caché, et c'est tout à fait normal. Comme indiqué par Simone, vous pouvez utiliser une instruction using pour amener A::f dans la portée C.

+0

+1 Bel article. Comme un point d'intérêt, cela est également discuté dans l'article 33 de Effective C++ Third Edition –

2

Insérer using B::f;

struct C : public A { 
    using A::f; 
    virtual void f() { cout << "C::f" << endl; } 
}; 

C++ standard 2003. 13.2 p.1:

Deux déclarations de fonctions du même nom font référence à la même fonction si elles sont dans la même portée et avoir des déclarations de paramètres équivalents (13.1). Une fonction membre d'une classe dérivée n'est pas dans la même portée en tant que membre de fonction du même nom dans une classe de base.

Ainsi C::f masque tous A::f.

+2

Ceci n'est pas compilé. Peut-être que vous voulez dire "A :: f". – Simone

+0

Désolé. Bien sûr, A :: f. –

3

Oui, c'est. Vous pouvez écrire:

struct C : public A { 
    virtual void f() { cout << "C::f" << endl; } 
    using A::f;  
}; 

pour rendre votre code compilation:

int main() 
{ 
    const B b; 
    b.f(); // prints "A::f const" 

    const C c; 
    c.f(); // prints "A::f const" 
} 

Pour plus d'infos, vous pouvez vous référer au chapitre 10.2 projet de Document de référence 2010 C++ (que vous pouvez trouver here) (3-. 4).

+0

+1 pour la référence à la norme, mais j'ai obtenu plus d'informations de la réponse de icecrime. Je connaissais bien l'option 'using'. Je n'étais pas intéressé à faire cette compilation, mais plutôt à comprendre la langue. – Ari

+0

Merci Ari, mais rappelez-vous que votre question peut être consultée par des personnes ayant le même problème et qui veulent la faire compiler. Il vaut mieux écrire quelque chose de plus que quelque chose de moins, êtes-vous d'accord? :) – Simone

3

Ce n'est pas la virtualité ou la constance (ou l'absence de constance) qui masque le membre de base, toute méthode dérivée cache une méthode de base du même nom. Cela a été fait pour améliorer le problème de la classe de base fragile.

Imaginez votre code fonctionnait (peut-être pendant des années) comme ci-dessous, avec des parties non pertinentes retirées:

struct Base { 
}; 

struct Derived : Base { 
    void f(double); 
} 

void g(Derived &d) { 
    d.f(42); 
} 

Ensuite, vous devez modifier la base pour inclure une méthode qui fait quelque chose de complètement différent, mais, pour une raison quelconque, vous voulez le nommer « f »:

struct Base { 
    void f(int); 
}; 

Sans cette règle, chaque utilisation d'un dérivé d'appel f doit être évaluée manuellement - et si la base est dans une bibliothèque donnée à d'autres personnes, vous ne pouvez même pas avoir accès à ces autres utilisations! Cela devient pire face aux conversions définies par l'utilisateur (implicite). Au lieu de cela, il a été décidé d'exiger que les classes dérivées indiquent explicitement qu'elles veulent importer des noms donnés à partir de Base avec une déclaration using. Cette règle peut être surprenante et je ne suis pas sûr que ce soit un avantage net pour la langue aujourd'hui, mais ils ne m'ont pas demandé - à l'époque, je n'aurais probablement pu leur répondre qu'avec des mots de deux syllabes, de toute façon. :)

Questions connexes