class Base {
public:
virtual void test() {};
virtual int get() {return 123;}
private:
int bob = 0;
};
class Derived: public Base{
public:
virtual void test() { alex++; }
virtual int get() { return alex;}
private:
int alex = 0;
};
Base* b = new Derived();
b->test();
当调用test
和get
时,会传入隐式this
指针。是不是因为Derived
类具有与内存布局相同的子内存布局纯基础对象,然后this
指针作为基指针和派生指针工作?
另一种说法是,Derived的内存布局就像
vptr <-- this
bob
alex
这就是为什么它可以在alex
中使用b->test()
,对吧?
答案 0 :(得分:2)
在Derived
方法内部,隐式this
指针始终是Derived*
指针(更一般地说,this
指针总是< / em>匹配被调用的类类型。这就是Derived::test()
和Derived::get()
可以访问Derived::alex
成员的原因。这与Base
无关。
Derived
对象的内存布局以Base
的数据成员开头,后跟可选填充,后跟Derived
的数据成员。这允许您在期望Derived
对象的任何地方使用Base
对象。当您将Derived*
指针传递给Base*
指针或Derived&
对Base&
引用的引用时,编译器将在编译时相应地调整指针/引用指向Base
对象的Derived
部分。
在运行时调用b->test()
时,b
是Base*
指针,编译器知道test()
是virtual
并将生成访问b
vtable中的相应位置,并调用指向的方法。但是,编译器并不知道派生对象类型b
在运行时实际指向的是什么(这是多态的整个魔力),因此它无法自动调整隐式{{1}在编译时指向正确的派生指针类型。
如果this
指向b
个对象,则Derived
的vtable指向b
的vtable。编译器知道从Derived
开始Derived
开始的确切偏移量。因此,Base
vtable中test()
的插槽将指向编译器生成的私有存根,以将隐式Derived
指针调整为Base *this
指针然后跳转到Derived *this
的实际实现代码。
在幕后,大致(不完全)实现如下伪代码:
Derived::test()
实际细节涉及更多,但这应该让您基本了解多态如何在运行时分派虚拟方法。
答案 1 :(得分:1)
您所展示的内容相当准确,至少对于典型的实施方式而言。它不能保证精确地显示它(例如,编译器可能很容易在bob
和alex
之间插入一些填充,但无论哪种方式它“知道”alex
处于一些预定义的偏移,因此它可以指向Base
,从中计算正确的偏移量,并使用那里的东西。
不是你提出的问题,所以我不会试图详细说明,但只是一个公平的警告:当/如果涉及多个继承时,计算这样的偏移可能/确实会变得更复杂一些。与访问最派生类的成员不同,但是如果访问基类的成员,它必须基本上计算到该基类开头的偏移量,然后添加偏移量以获得正确的偏移量那个基类。
答案 2 :(得分:0)
派生类不是单独的类,而是扩展。如果某事被分配为派生,那么指针(它只是内存中的地址)将能够从派生类中找到所有内容。程序集中不存在类,编译器根据内存中的分配方式跟踪所有内容,并相应地提供相应的检查。