首页 > 解决方案 > 为什么基指针可以访问虚函数中的派生成员变量

问题描述

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();

testget被调用时,隐式this指针被传入。是因为Derived类具有与纯基对象相同的子内存布局,那么this指针既可用作基指针又可用作派生指针?

另一种说法是,Derived 的内存布局就像

vptr <-- this
bob
alex

这就是它可以在 in 中使用alex的原因b->test(),对吧?

标签: c++polymorphism

解决方案


在 ofDerived的方法中,隐式this指针始终是Derived*指针(更一般地说,this指针始终与被调用的类类型匹配)。这就是为什么Derived::test()并且Derived::get()可以访问该Derived::alex成员。那与 . 无关Base

对象的内存布局Derived以 的数据成员开始Base,然后是可选的填充,然后是 的数据成员Derived。这允许您在需要对象的Derived任何地方使用Base对象。当您将指针传递给Derived*指针Base*Derived&引用时Base&,编译器将在编译时相应地调整指针/引用以指向对象的Base一部分Derived

当您b->test()在运行时调用 whereb是一个Base*指针时,编译器知道test()并且virtual将生成访问b的 vtable 中的适当插槽并调用所指向的方法的代码。但是,编译器不知道b在运行时实际指向的派生对象类型(这就是多态的全部魔力),因此它不能this在编译时自动将隐式指针调整为正确的派生指针类型。

在 whereb指向Derived对象的情况下,b的 vtable 指向Derived的 vtable。编译器知道Derived从. 开始的确切偏移量Base。因此,test()inDerived的 vtable 的插槽将指向编译器生成的私有存根,以将隐式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()...

实际的细节涉及更多,但这应该让您对多态性如何能够在运行时调度虚拟方法有一个基本的了解。


推荐阅读