我知道,对于任何一个有虚函数的类,或者一个从有虚函数的类派生的类,编译器会做两件事:第一,为该类创建一个虚表;第二,在对象的基类部分放置一个虚指针(vptr);在运行时,这个vptr被赋值,并在对象示例化时开始指向正确的vtable。我的问题是,在示例化过程中,这个vptr是在哪里被设置的?这个vptr的赋值是在构造函数之前还是之后的对象的构造函数内部发生的?
zdwk9cvp1#
这完全取决于实施。对于大多数编译器,编译器在每个构造函数的成员初始化器列表中初始化this->__vptr。其思想是让每个对象的v指针指向其类的v表,编译器为此生成隐藏代码并将其添加到构造函数代码中,类似于:
this->__vptr
Base::Base(...arbitrary params...) : __vptr(&Base::__vtable[0]) ← supplied by the compiler, hidden from the programmer { }
本C++常见问题解答解释了实际发生的事情的要点。
flvtvl502#
指向vtable的指针在进入层次结构中的每个构造函数时更新,然后在进入每个析构函数时再次更新。vptr将开始指向基类,然后随着不同级别的初始化而更新。虽然你会从许多不同的人那里读到这是实现定义的,因为它是vtable的全部选择,但事实是所有的编译器都使用vtable,一旦你选择了vtable方法,标准就会强制要求 * 运行时对象的类型是正在执行的构造函数/析构函数的类型 *,这反过来意味着无论动态调度机制是什么,它必须随着构造/破坏链的遍历而调整。请考虑以下代码片段:
#include <iostream> struct base; void callback( base const & b ); struct base { base() { callback( *this ); } ~base() { callback( *this ); } virtual void f() const { std::cout << "base" << std::endl; } }; struct derived : base { derived() { callback( *this ); } ~derived() { callback( *this ); } virtual void f() const { std::cout << "derived" << std::endl; } }; void callback( base const & b ) { b.f(); } int main() { derived d; }
标准要求该程序的输出为base、derived、derived、base,但是callback中的调用与对该函数的所有四个调用都是相同的,实现它的唯一方法是随着构造/销毁的进展更新对象中的vptr。
base
derived
callback
bgibtngc3#
This msdn article explains it in great detali上面写着:“最后的答案是......正如你所料,它发生在构造函数中。”如果我可以添加的话,就在构造函数的开头,在构造函数中的任何其他代码执行之前。但是要小心,假设你有一个类A,和一个从A派生的类A1。
“下面是构造类A1的示例时的整个事件序列:
e4yzc0pl4#
在构造函数的主体中,可以调用虚函数,因此,如果实现使用vptr,则vptr已经被设置。注意,在ctor中调用的虚函数是在 that 构造函数的类中定义的,而不是可能被更派生的类覆盖的虚函数。
vptr
#include <iostream> struct A { A() { foo (); } virtual void foo () { std::cout << "A::foo" << std::endl; } }; struct B : public A { virtual void foo () { std::cout << "B::foo" << std::endl; } }; int main () { B b; // prints "A::foo" b.foo (); // prints "B::foo" return 0; }
dtcbnfnu5#
虽然它依赖于实现,但实际上它必须在构造函数本身的主体求值之前发生,因为根据C规范,这是允许的(12.7/3),通过构造函数体中的this指针访问非静态类方法......因此,必须在调用构造函数体之前设置vtable,否则通过this指针调用虚类方法将无法正常工作。尽管this指针和vtable是两个不同的东西,事实上Cstandard允许在构造函数体中使用this指针,演示了编译器必须如何实现标准的vtable-至少从计时的Angular 来看,this指针的兼容使用才能正常工作。如果在调用构造函数主体期间或之后初始化了vtable,则使用this指针来调用构造函数体内部的虚函数或将this指针传递给依赖于动态分派的函数将是有问题的产生未定义的行为。
this
5条答案
按热度按时间zdwk9cvp1#
这完全取决于实施。
对于大多数编译器,
编译器在每个构造函数的成员初始化器列表中初始化
this->__vptr
。其思想是让每个对象的v指针指向其类的v表,编译器为此生成隐藏代码并将其添加到构造函数代码中,类似于:
本C++常见问题解答解释了实际发生的事情的要点。
flvtvl502#
指向vtable的指针在进入层次结构中的每个构造函数时更新,然后在进入每个析构函数时再次更新。vptr将开始指向基类,然后随着不同级别的初始化而更新。
虽然你会从许多不同的人那里读到这是实现定义的,因为它是vtable的全部选择,但事实是所有的编译器都使用vtable,一旦你选择了vtable方法,标准就会强制要求 * 运行时对象的类型是正在执行的构造函数/析构函数的类型 *,这反过来意味着无论动态调度机制是什么,它必须随着构造/破坏链的遍历而调整。
请考虑以下代码片段:
标准要求该程序的输出为
base
、derived
、derived
、base
,但是callback
中的调用与对该函数的所有四个调用都是相同的,实现它的唯一方法是随着构造/销毁的进展更新对象中的vptr。bgibtngc3#
This msdn article explains it in great detali
上面写着:
“最后的答案是......正如你所料,它发生在构造函数中。”
如果我可以添加的话,就在构造函数的开头,在构造函数中的任何其他代码执行之前。
但是要小心,假设你有一个类A,和一个从A派生的类A1。
“下面是构造类A1的示例时的整个事件序列:
e4yzc0pl4#
在构造函数的主体中,可以调用虚函数,因此,如果实现使用
vptr
,则vptr
已经被设置。注意,在ctor中调用的虚函数是在 that 构造函数的类中定义的,而不是可能被更派生的类覆盖的虚函数。
dtcbnfnu5#
虽然它依赖于实现,但实际上它必须在构造函数本身的主体求值之前发生,因为根据C规范,这是允许的(12.7/3),通过构造函数体中的
this
指针访问非静态类方法......因此,必须在调用构造函数体之前设置vtable,否则通过this
指针调用虚类方法将无法正常工作。尽管this
指针和vtable是两个不同的东西,事实上Cstandard允许在构造函数体中使用this
指针,演示了编译器必须如何实现标准的vtable-至少从计时的Angular 来看,this
指针的兼容使用才能正常工作。如果在调用构造函数主体期间或之后初始化了vtable,则使用this
指针来调用构造函数体内部的虚函数或将this
指针传递给依赖于动态分派的函数将是有问题的产生未定义的行为。