2010-11-11 89 views
7

考虑:重写非常量虚拟方法是否隐藏常量重载?

#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 
} 

(我使用GCC)

如此看来,f的const版本()获取隐藏在C.这使得有很大的意义对我来说,却是它是由标准授权的吗?

+4

“虚拟”是一个红色的鲱鱼。我们在这里几乎没有(通过基类指针或引用)调用任何'f'。所有'f'的查找都可以找到最多的'f'。 – MSalters 2010-11-11 09:34:48

+0

虚拟和const并不真正适用于这个问题,但我将它们留作标记,因为我没有看到太多的伤害,也没有必要包含更相关的标记。 – 2010-11-11 09:53:16

+2

我同意'虚拟',但'const'是整个问题的意思。重写'f()'隐藏'f()const'。 – Ari 2010-11-11 12:11:07

回答

4

我将(再次)链接这个伟大的article

首先,[编译]看在 最近的范围内,在这种情况下,C类的 范围,使得 列表它可以找到的所有功能都是 ,命名为f(不管它们是否可以访问 ,甚至可以采用正确的 多个参数)。 只有当它 没有它,然后继续 “向外”到下一个封闭的 范围 [...]

所以,是的,在const版本的f是隐藏的,这是完全正常的。正如Simone所指出的那样,您可以使用using声明将A::f带入C范围。

+0

+1尼斯文章。作为感兴趣的一点,这也在Effective C++ Third Edition的第33项中讨论 – 2010-11-11 09:53:17

2

插入using B::f;

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

C++标准2003 13.2 P.1:

两个同名函数声明指代相同功能 如果它们是在相同的范围和 有相同的参数声明(13.1)。一个函数 派生类的成员不是在 相同 范围作为基类中同名的函数成员。

因此C::f隐藏全部A::f

+2

Thid不能编译。也许你的意思是“A :: f”。 – Simone 2010-11-11 09:22:16

+0

对不起。当然,A :: f。 – 2010-11-11 09:23:15

3

是的。你可以写:

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

使你的代码编译:

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

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

欲了解更多相关信息,您可以参考2010年的C++文件草案的(你可以找到here)章节10.2(3-。 4)。

+0

+1为参考标准,但我从icecrime的答案中获得更多信息。我很清楚'使用'选项。我对这个编译不感兴趣,而是对这个语言的理解。 – Ari 2010-11-11 12:40:58

+0

谢谢Ari,但请记住,你的问题可能会被同样问题的人咨询,他们也想编译它。最好是写更多的东西,而不是更少,你同意吗? :) – Simone 2010-11-11 13:07:22

3

隐藏基本成员不是虚拟性或常量性(或缺乏),任何派生方法都隐藏一个同名的基本方法。这是为了改善脆弱的基类问题。

想象一下你的代码是工作(可能是数年)如下,以去除不相关的部分:

struct Base { 
}; 

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

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

然后,你需要修改基地包括会产生完全不同的方法,但是,对于由于某些原因,你需要将其命名“F”:

struct Base { 
    void f(int); 
}; 

没有这一条规则,需要手动评估每一个使用派生调用的F - 如果基地是在图书馆给其他人,你甚至可能不会有机会获得这些其他用途!面对用户定义(隐式)转换,情况会变得更糟。

相反,决定要求派生类明确声明他们想要使用using声明从Base导入给定名称。这条规则可能令人惊讶,我不确定这对今天的语言是否有利,但他们并没有问我 - 当时,我可能只是用双音节词回答他们。 :)