现在的位置: 首页 > 综合 > 正文

钻穿牛角尖:using声明vs虚函数

2013年07月31日 ⁄ 综合 ⁄ 共 1685字 ⁄ 字号 评论关闭

转载自CView:虫虫的文章

问题:

下列程序输出结果是什么?

#include <iostream> 
using namespace std; 
struct A 
{ 
        virtual void f() {cout<<"A::f"<<endl;} 
}; 
struct B : A 
{ 
        virtual void f() {cout<<"B::f"<<endl;} 
}; 
struct C : B 
{ 
        using A::f; 
}; 
void main() 
{ 
        C c; 
        c.f(); 
        c.C::f(); 
}

解答:

         当然,运行一下不就知道了?不过您在不同的编译器运行,结果可能千差万别。gcc2.9x上输出两个B::f,gcc3.0上又输出两个A::f。怎么回事呢?

         c.f()与c.C::f()有什么区别?这看起来像是个简单的问题,很多人都会以为它们是等价的,然而这道题的结果恐怕又让您大跌眼镜,正确结果应该是B::f和A::f。(注:这个问题改编自C++标准ISO/IEC 14882第169页,有兴趣的朋友不妨看看。)在BorlandC++Compiler5.5.1和IntelC/C++Compiler5.0上的结果均正确。

        c.C::f()输出A::f我相信大家不会感到意外,因为在类C中明确地有using A::f这么一行,C::f当然就等价于A::f了,可是c.f()怎么又回调用B::f呢?查查标准吧,在168页有这么一句:The rules for member lookup are used to determine the final overrider for a virtual function in the scope of a derived class but ignoring names
introduced by using-declarations.大意是说,using声明(using-declaration)和虚函数两不相干。既然不相干,c.f()得到这样的结果页就不足为奇。

        但是别忙。c.f()是动态调用吗?我们知道,动态调用一般是以基类指针或引用的形式,但是这里的c既不是指针或引用,也不是基类,怎么会是动态调用呢?

        以BorlandC++Compiler5.5.1为例,我们来看看c.f()和c.C::f()的汇编代码。

        c.f();
        lea eax,[ebp-0x04]
        push eax
        mov edx,[ebp-0x04]
        call dword ptr [edx]
        pop ecx

       c.C::f();
       lea ecx,[ebp-0x04] 
       push ecx
       call A::f()
       pop ecx

       不懂汇编的朋友也不用着急,只看加黑得两行即可。c.f()中有call dword ptr [edx]这么一句,这是读取虚拟函数表VFT(Virtual Function Table),进行动态调用;而在c.C::f()中直接call a::f(),是静态调用,在编译阶段就已经固定下来。

       c.f()果然是动态调用的,这也是Borland和Intel两个编译器结果正确的关键,而GCC 2.9x和3.0就载在这里。按我们的经验,c.f()一般是静态调用,但这里却是动态,why?

       注意一个关键所在,C::f是继承自它的祖先类,类C本身是没有函数f()。在这种情况下,c.f()就必须进行动态调用,因为我们无法在编译的时候确定C::f。所以如果我们把类C改为

        struct C : B

        {

               using A::f;

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

        };
        一旦我们重写了函数f,c.f()和c.C::f()就是完全等价的,编译时就静态绑定,都会输出C::f。注意,这时候别被那个using A::f;迷惑了,C本身有了f,就不会再用祖先类的f了。

抱歉!评论已关闭.