C ++:虚函数如何解决“this”指针范围问题?

时间:2012-04-24 06:44:49

标签: c++ virtual-functions mingw32

(C ++,MinGW 4.4.0,Windows OS)

代码中注释的所有内容除了标签< 1>和< 2>,是我的猜测。如果你认为我错了,请纠正我:

class A {
public:
   virtual void disp(); //not necessary to define as placeholder in vtable entry will be
                        //overwritten when derived class's vtable entry is prepared after
                        //invoking Base ctor (unless we do new A instead of new B in main() below)
};

class B :public A {
public:
   B() : x(100) {}
   void disp() {std::printf("%d",x);}
   int x;
};

int main() {
   A* aptr=new B;             //memory model and vtable of B (say vtbl_B) is assigned to aptr
   aptr->disp();              //<1> no error
   std::printf("%d",aptr->x); //<2> error -> A knows nothing about x
}

&LT 2 - ;是一个错误,很明显。为什么&lt; 1&gt;不是错误吗?我认为这次调用的结果是:参数中的aptr->disp(); --> (*aptr->*(vtbl_B + offset to disp))(aptr) aptr是指向成员函数的隐式this指针。在disp()内,我们会std::printf("%d",x); --> std::printf("%d",aptr->x); SAME AS std::printf("%d",this->x);为什么&lt; 1&gt; &lt; 2&gt;时没有错误呢?

(我知道vtables是特定于实现的东西,但我仍然认为值得提问)

4 个答案:

答案 0 :(得分:3)

规则是:

  

在C ++中,动态分派仅适用于成员函数,而不适用于成员变量。

对于成员变量,编译器仅查找该特定类或其基类中的符号名称。

在案例1中,要调用的适当方法取决于获取 vpt获取相应方法的地址,然后调用适当的成员函数 因此,在静态绑定的情况下,动态调度基本上是fetch-fetch-call而不是正常call

案例2:编译器仅查找x范围内的this。显然,它找不到它并报告错误。

答案 1 :(得分:3)

thisaptr内的B::disp不同。 B::disp实施将this作为B*,与B的任何其他方法一样。当您通过A*指针调用虚方法时,它首先转换为B*(甚至可能会更改其值,因此在调用期间它不一定等于aptr

即。真正发生的事情就像是

typedef void (A::*disp_fn_t)();
disp_fn_t methodPtr = aptr->vtable[index_of_disp]; // methodPtr == &B::disp

B* b = static_cast<B*>(aptr);
(b->*methodPtr)(); // same as b->disp()

有关更复杂的示例,请查看此帖子http://blogs.msdn.com/b/oldnewthing/archive/2004/02/06/68695.aspx。这里,如果存在可以调用相同A的多个B::disp个基数,则MSVC生成不同的入口点,每个入口点通过不同的偏移量移位A*指针。当然,这是特定于实现的;其他编译器可能会选择将偏移量存储在vtable中的某个位置。

答案 2 :(得分:1)

你很困惑,在我看来你来自更动态的语言。

在C ++中,编译和运行时明显是隔离的。必须首先编译程序然后才能运行(并且任何这些步骤都可能失败)。


所以,后退:

<2>在编译时失败,因为编译是关于静态的信息。 aptr的类型为A*,因此A的所有方法和属性都可通过此指针访问。由于您声明了disp()但没有x,因此对disp()的调用进行了编译,但没有x

因此,<2>的失败与语义有关,而这些是在C ++标准中定义的。


转到<1>,因为disp()中有A声明,所以它有效。这保证了函数的存在(我会说你实际上在这里,因为你没有在A定义它。)

运行时发生的事情是由C ++标准语义定义的,但标准没有提供实现指南。大多数(如果不是全部)C ++编译器将使用每个类的虚拟表+每个实例策略的虚拟指针,并且在这种情况下您的描述看起来是正确的。

然而,这是纯运行时实现,并且它运行的事实不会追溯性地影响程序编译的事实。

答案 3 :(得分:0)

virtual void disp(); //not necessary to define as placeholder in vtable entry will be
                     //overwritten when derived class's vtable entry is prepared after
                     //invoking Base ctor (unless we do new A instead of new B in main() below)

您的评论不严格。虚函数是 odr-used ,除非它是纯的(反过来不一定保持),这意味着你必须为它提供一个定义。如果您不想为它提供定义,则必须使其成为纯虚函数。

如果您进行了其中一项修改,那么aptr->disp();将起作用并调用派生类disp(),因为派生类中的disp()会覆盖基类函数。当您通过指向base的指针调用它时,基类函数仍然必须存在。 x不是基类的成员,因此aptr->x不是有效的表达式。