簡體   English   中英

為什么基本指針可以在虛擬函數中訪問派生成員變量

[英]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();

調用testget ,將傳入隱式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()時(其中bBase*指針),編譯器知道test()virtual並且將生成訪問b的vtable中相應插槽的代碼並調用所指向的方法。 但是,編譯器不知道運行時實際上指向的是什么派生對象類型b (這就是多態的全部魔力),因此它無法在編譯時自動將隱式this指針調整為正確的派生指針類型。 。

b指向Derived對象的情況下, b的vtable指向Derived的vtable。 編譯器知道Base的起點從Derived的起點的確切偏移量。 因此, Derived的vtable中的test()插槽將指向編譯器生成的私有存根,以將隱式Base *this指針調整為Derived *this指針,然后再跳轉到Derived::test()的實際實現代碼中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()...

實際的細節要多一些,但這應該使您基本了解多態如何在運行時調度虛擬方法。

您所顯示的內容相當准確,至少對於典型的實現而言是這樣。 不能保證完全如您所展示的那樣(例如,編譯器可以輕松地在bobalex之間插入一些填充,但是無論哪種方式,它都“知道” alex與該位置有一些預定義的偏移,因此它可以使用一個指針到Base ,從中計算出正確的偏移量,然后使用那里的值。

不是您的要求,所以我不會嘗試詳細介紹,而只是一個合理的警告:當涉及多重繼承時,計算此類偏移可能/確實會變得更加復雜。 訪問最派生類的成員並不需要那么多,但是如果您訪問基類的成員,則它基本上必須計算該基類開始處的偏移量,然后添加一個偏移量以獲取到其中的正確偏移量該基類。

派生類不是單獨的類,而是擴展。 如果分配了某些東西作為派生對象,則指針(這只是內存中的地址)將能夠從派生類中找到所有內容。 類在匯編中不存在,編譯器根據所有內容在內存中的分配方式跟蹤所有內容,並相應地提供適當的檢查。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM