C++ C++;:虚拟函数是如何解决的;这";指针范围问题?
(C++,MingW4.4.0,Windows操作系统) 代码中所有的注释,除了标签和,都是我的猜测。如果你认为我错了,请纠正我:C++ C++;:虚拟函数是如何解决的;这";指针范围问题?,c++,virtual-functions,mingw32,C++,Virtual Functions,Mingw32,(C++,MingW4.4.0,Windows操作系统) 代码中所有的注释,除了标签和,都是我的猜测。如果你认为我错了,请纠正我: 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 pr
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
}
A类{
公众:
virtual void disp();//不需要在vtable条目中定义为占位符
//在之后准备派生类的vtable条目时覆盖
//调用基ctor(除非我们在下面的main()中使用新的A而不是新的B)
};
B类:公共A{
公众:
B():x(100){}
void disp(){std::printf(“%d”,x);}
int x;
};
int main(){
A*aptr=new B;//将B的内存模型和vtable(比如vtbl_B)分配给aptr
aptr->disp();//无错误
std::printf(“%d”,aptr->x);//错误->A对x一无所知
}
这是一个明显的错误。为什么不是一个错误?我认为这个调用所发生的是:aptr->disp();-->(*aptr->*(vtbl_B+偏移到disp))(aptr)
aptr
中的参数是指向成员函数的隐式指针。在disp()
内部,我们将有std::printf(“%d”,x);-->标准::printf(“%d”,aptr->x);与std::printf相同(“%d”,此->x)代码>那么为什么不给出错误而给出错误呢
(我知道vtables是特定于实现的东西,但我仍然认为值得问这个问题)规则是:
< C++ >动态调度只适用于成员函数不为成员变量的函数。>/P>
对于成员变量,编译器仅查找该特定类或其基类中的符号名称
在案例1中,通过获取vpt
,获取适当方法的地址,然后调用适当的成员函数来确定要调用的适当方法。
因此,在静态绑定的情况下,动态调度本质上是一个fetch调用
,而不是正常的调用
在案例2中:编译器只在this
的范围内查找x
,显然,它找不到它并报告错误
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使用的,除非它是纯函数(反之不一定成立),这意味着您必须为它提供定义。如果你不想为它提供一个定义,你必须使它成为一个纯粹的虚拟函数
如果进行这些修改之一,则aptr->disp()
工作并调用派生类disp()
,因为派生类中的disp()
重写基类函数。当您通过指向基类的指针调用基类函数时,基类函数仍然必须存在x
不是基类的成员,因此aptr->x
不是有效的表达式。此
与B::disp
中的aptr
不同。B::disp
实现将这个作为B*
,就像B
的任何其他方法一样。当您通过A*
指针调用virtual方法时,它首先被转换为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()
typedef void(A::*disp_fn_t)();
显示方法ptr=aptr->vtable[显示的索引];//methodPtr==&B::disp
B*B=静态_-cast(aptr);
(b->*方法ptr)(;//与b->disp()相同
有关更复杂的示例,请查看此帖子。这里,如果有多个A
基可以调用相同的B::disp
,则MSVC生成不同的入口点,每个入口点将A*
指针移动不同的偏移量。当然,这是具体实施的;例如,其他编译器可能会选择将偏移量存储在vtable中的某个位置。您感到困惑,在我看来,您来自更具动态性的语言
在C++中,编译和运行时是清晰分离的。程序必须先编译,然后才能运行(这些步骤中的任何一个都可能失败)
那么,回过头来看:
编译失败,因为编译是关于静态信息的aptr
属于A*
类型,因此可以通过该指针访问A
的所有方法和属性。由于声明了disp()
但没有x
,因此对disp()
的调用将编译,但没有x
<> P> >代码> <代码>的失败是关于语义的,而这些是在C++标准中定义的。
进入
,它可以工作,因为a
中有一个disp()
声明。这保证了函数的存在(我要说的是,您实际上位于这里,因为您没有在A
中定义它)
<> P>运行时发生的事情是由C++标准语义定义的,但标准没有提供实现指导。大多数(如果不是全部)C++编译器将使用每个类的虚拟表+每个实例的虚拟指针策略,并且在这种情况下,您的描述看起来正确。
但是,这是纯粹的运行时实现,它运行的事实不会追溯到程序编译的事实。ok,但此被切片为只了解A
。在成员函数内部,我认为调用解析为this->whatEver
。让我困惑的是,由于所示的隐式传递的this
与B对象的传递方式不同,机制是如何工作的?@ustulation:No,this
属于B
类型,而调用的方法是B::disp()
,当然,它可以访问x
,这是它自己的成员。这里没有切片,真正的规则是C++,动态调度只用于VILT。