为什么基指针可以访问虚拟函数中的派生成员变量
Why does a base pointer can access derived member variable in virtual funtion
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
这就是为什么它可以在b->test()
中使用alex
,对吧?
在Derived
的方法中,隐式this
指针始终是Derived*
指针(更一般地说,this
指针始终与被调用的类类型匹配)。这就是为什么Derived::test()
和Derived::get()
可以访问Derived::alex
成员的原因。 这与Base
无关.
Derived
对象的内存布局以Base
的数据成员开头,然后是可选的填充,后跟Derived
的数据成员。 这允许您在需要Base
对象的任何位置使用Derived
对象。 将Derived*
指针传递给Base*
指针或将Derived&
引用传递给Base&
引用时,编译器将在编译时相应地调整指针/引用,以指向Derived
对象的Base
部分。
当你在运行时调用b->test()
时,其中b
是一个Base*
指针,编译器知道test()
是virtual
,并将生成访问b
vtable中相应插槽的代码,并调用所指向的方法。 但是,编译器不知道b
在运行时实际指向的派生对象类型(这是多态性的全部魔力),因此它无法在编译时自动将隐式this
指针调整为正确的派生指针类型。
在b
指向Derived
对象的情况下,b
的 vtable 指向Derived
的 vtable。 编译器知道Derived
开始与Base
开始的确切偏移量。 因此,Derived
的 vtable 中的test()
插槽将指向编译器生成的私有存根,以将隐式Base *this
指针调整为Derived *this
指针,然后再跳转到实际的实现代码进行Derived::test()
。
在幕后,它大致(不完全)像以下伪代码一样实现:
void Derived_test_stub(Base *this)
{
Derived *adjusted_this = reinterpret_cast<Derived*>(reinterpret_cast<uintptr_t>(this) + offset_from_Base_to_Derived);
Derived::test(adjusted_this);
}
int Derived_get_stub(Base *this)
{
Derived *adjusted_this = reinterpret_cast<Derived*>(reinterpret_cast<uintptr_t>(this) + offset_from_Base_to_Derived);
return Derived::get(adjusted_this);
}
struct vtable_Base
{
void* funcs[2] = {&Base::test, &Base::get};
};
struct vtable_Derived
{
void* funcs[2] = {&Derived_test_stub, &Derived_get_stub};
};
Base::Base()
{
this->vtable = &vtable_Base;
bob = 0;
}
Derived::Derived() : Base()
{
Base::vtable = &vtable_Derived;
this->vtable = &vtable_Derived;
alex = 0;
}
...
Base *b = new Derived;
//b->test(); // calls Derived::test()...
typedef void (*test_type)(Base*);
static_cast<test_type>(b->vtable[0])(b); // calls Derived_test_stub()...
//int i = b->get(); // calls Derived::get()...
typedef int (*get_type)(Base*);
int i = static_cast<get_type>(b->vtable[1])(b); // calls Derived_get_stub()...
实际的细节要复杂一些,但这应该让你对多态如何在运行时调度虚拟方法有一个基本的了解。
你展示的内容相当准确,至少对于典型的实现而言是这样。它不能保证与您显示的精确一样(例如,编译器可能很容易在bob
和alex
之间插入一些填充,但无论哪种方式,它都"知道"alex
处于某种预定义的偏移量,因此它可以获取指向Base
的指针,计算正确的偏移量,并使用那里的内容。
不是你问的,所以我不会试图详细介绍,而只是一个公平的警告:当/如果涉及多重继承时,计算这种偏移量可能会/确实会变得更加复杂。与其说是访问派生最多类的成员,不如说是访问基类的成员,它必须基本上计算到该基类开头的偏移量,然后添加一个偏移量以获得该基类中的正确偏移量。
派生类不是一个单独的类,而是一个扩展。如果某些东西被分配为派生的,那么指针(只是内存中的一个地址)将能够从派生类中找到所有内容。程序集中不存在类,编译器会根据其在内存中的分配方式跟踪所有内容,并相应地提供适当的检查。
- 为什么使用 "this" 指针调用派生成员函数?
- 具有奇怪重复模板模式的派生类中的成员变量已损坏
- 如何使用基类指针引用派生类成员
- 派生类是否可以在抽象工厂设计模式中具有数据成员
- 在 C++ 中用派生类型重写成员函数
- 如果基类包含双指针成员,则派生类的构造函数
- 在派生类中绑定非静态模板化成员函数
- 为什么基指针可以访问虚拟函数中的派生成员变量
- 在销毁派生成员之前,在破坏者中调用共同功能
- 带有派生成员的基类的独特指针
- 为什么基类对象的私有成员仍然从派生成员中分配
- 派生成员函数指针的多态性
- 派生成员和重载Ostream运算符
- 通过指向派生类的指针访问派生成员变量
- 在构造过程中是否有一种简洁的方法可以派生成员的类型?
- 如何使用派生类特定的方法:派生类中的派生成员
- 获取从基类指针到具有不同类型的两个派生成员变量的访问权
- 当我使用根据基类定义的成员函数指针时,编译器如何调用派生成员函数?
- 将luabind派生成员调用为协同程序
- C++ POD 结构继承?是否有任何关于派生成员的内存布局的保证