![](/img/trans.png)
[英]Why can I access a derived private member function via a base class pointer to a derived object?
[英]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。 編譯器知道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()...
實際的細節要多一些,但這應該使您基本了解多態如何在運行時調度虛擬方法。
您所顯示的內容相當准確,至少對於典型的實現而言是這樣。 不能保證完全如您所展示的那樣(例如,編譯器可以輕松地在bob
和alex
之間插入一些填充,但是無論哪種方式,它都“知道” alex
與該位置有一些預定義的偏移,因此它可以使用一個指針到Base
,從中計算出正確的偏移量,然后使用那里的值。
不是您的要求,所以我不會嘗試詳細介紹,而只是一個合理的警告:當涉及多重繼承時,計算此類偏移可能/確實會變得更加復雜。 訪問最派生類的成員並不需要那么多,但是如果您訪問基類的成員,則它基本上必須計算該基類開始處的偏移量,然后添加一個偏移量以獲取到其中的正確偏移量該基類。
派生類不是單獨的類,而是擴展。 如果分配了某些東西作為派生對象,則指針(這只是內存中的地址)將能夠從派生類中找到所有內容。 類在匯編中不存在,編譯器根據所有內容在內存中的分配方式跟蹤所有內容,並相應地提供適當的檢查。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.