(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是特定于实现的东西,但我仍然认为值得提问)
答案 0 :(得分:3)
规则是:
在C ++中,动态分派仅适用于成员函数,而不适用于成员变量。
对于成员变量,编译器仅查找该特定类或其基类中的符号名称。
在案例1中,要调用的适当方法取决于获取 vpt
,获取相应方法的地址,然后调用适当的成员函数
因此,在静态绑定的情况下,动态调度基本上是fetch-fetch-call
而不是正常call
。
案例2:编译器仅查找x
范围内的this
。显然,它找不到它并报告错误。
答案 1 :(得分:3)
this
与aptr
内的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
不是有效的表达式。