C++ 鏈式繼承下的虛擬函式列表
目錄
1.虛擬函式列表的位置
2.虛擬函式列表的內容
3.鏈式繼承中虛擬函式列表的內容
注:
虛擬函式列表 又稱為虛表, vtbl , 指向它的指標稱為vptr, vs2019中稱為__vfptr
作業系統: windows 10 專業版 64位
編譯器: Visual Studio 2019 Community
1.虛擬函式列表的位置
結論
編譯器一般會保證指向虛擬函式列表的指標存在於物件例項中最前面的位置
而虛擬函式列表中的內容, 就是多個函式指標
程式碼驗證:
首先宣告一個基類Base
和一個派生類Derived
class Base { public: virtual void f() { std::cout << "Base1::f" << std::endl; } virtual void g() { std::cout << "Base1::g" << std::endl; } virtual void h() { std::cout << "Base1::h" << std::endl; } virtual void i() { std::cout << "Base1::i" << std::endl; } }; class Derived : public Base { virtual void g() override { std::cout << "Derived::g" << std::endl; } virtual void h1() { std::cout << "Derived::i1" << std::endl; } };
然後例項化一個派生類的物件
Derived derived;
現在我們打印出該物件的地址
std::cout << "derived物件的地址: " << (&derived) << std::endl;
由於我們假定指向虛擬函式列表的<指標>存在於物件例項中最前面的位置
那麼我們可以認定, derived物件的地址
中的開頭是一個指標
的地址(稱之為指標pA)
而這個指標(pA)指向虛擬函式列表中的開頭, 也就是一個函式指標(稱之為指標pF)
所以這個指標(pA), 是一個指向指標的指標
, 即指向指標(pF)的指標(pA)
基於這個推測, 我們將derived物件的地址
指標pA的地址
進行一個型別轉換
使用reinterpret_cast<int**>
關鍵字, 將其轉換為一個指向指標的指標
reinterpret_cast<int**>(&derived)
現在我們對這個指標(pA)的地址, 取其內容, 就會得到pA的內容
std::cout << "derived物件中第一個指標的內容: " << *reinterpret_cast<int**>(&derived) << std::endl;
根據上面的推測, 這個內容, 就是虛擬函式列表的地址
控制檯輸出如下:
通過vs2019中, 可以直接檢視到derived
2.單繼承中虛擬函式列表的內容
基類中有4個函式, 分別為
f();
g();
h();
i();
派生類中有2個函式,分別為
g();
i1();
現在使用表格的方式表示出來, 方便檢視, 進行了override的函式, 會放在同一行
結論 在虛擬函式列表中, 函式的佈局如下圖所示:
程式碼驗證請看鏈式繼承中虛擬函式列表的內容
3.鏈式繼承中虛擬函式列表的內容
宣告3個類, 其繼承關係為Derived
繼承Base2
, Base2
繼承Base1
class Base1 {
public:
virtual void f() { std::cout << "Base1::f" << std::endl; }
virtual void g() { std::cout << "Base1::g" << std::endl; }
virtual void h() { std::cout << "Base1::h" << std::endl; }
virtual void i() { std::cout << "Base1::i" << std::endl; }
};
class Base2 : public Base1{
public:
virtual void f()override { std::cout << "Base2::f" << std::endl; }
virtual void h1() { std::cout << "Base2::h1" << std::endl; }
};
class Derived : public Base2 {
public:
virtual void g()override { std::cout << "Derived::g" << std::endl; }
virtual void i1() { std::cout << "Derived::i1" << std::endl; }
};
用表格的方法表示為:
!()(https://silenzio-markdown-image-hosting-service.oss-cn-beijing.aliyuncs.com/%E5%8D%9A%E5%AE%A2%E5%9B%BE%E5%BA%8A/C%2B%2B%20%E9%93%BE%E5%BC%%E7%BB%A7%E6%89%BF%E4%B8%8B%E7%9A%84%E8%99%9A%E5%87%BD%E6%95%B0%E5%88%97%E8%A1%A8/1a885406e3318ccbbf5dce9961e1599.png)
結論
在虛擬函式列表中, 函式的佈局如下圖所示:
Derive只有一個虛擬函式表, 是在Base2的虛擬函式表上, 進行類似於單繼承的覆蓋
同理, Base2也有一張虛擬函式表, 是在Base1的虛擬函式表上, 進行單繼承的覆蓋
程式碼驗證
////////////////////////////////////////////////////////////////////////////////
// 鏈式繼承
////////////////////////////////////////////////////////////////////////////////
#include <iostream>
class Base1 {
public:
virtual void f() { std::cout << "Base1::f" << std::endl; }
virtual void g() { std::cout << "Base1::g" << std::endl; }
virtual void h() { std::cout << "Base1::h" << std::endl; }
virtual void i() { std::cout << "Base1::i" << std::endl; }
};
class Base2 : public Base1{
public:
virtual void f()override { std::cout << "Base2::f" << std::endl; }
virtual void h1() { std::cout << "Base2::h1" << std::endl; }
};
class Derived : public Base2 {
public:
virtual void g()override { std::cout << "Derived::g" << std::endl; }
virtual void i1() { std::cout << "Derived::i1" << std::endl; }
};
using Fun = void(*)(void);
int main()
{
Fun pFun = nullptr;
// 作業系統: windows 10 專業版 32/64位都可以
// 編譯器 : Visual Studio 2019 Community
std::cout << sizeof(int) << std::endl; // 32位:4 64位:4
std::cout << sizeof(long) << std::endl; // 32位:4 64位:4
std::cout << sizeof(int*) << std::endl; // 32位:4 64位:8
std::cout << "-------------------------------------------------------------------------------------- " << std::endl;
std::cout << "Base1的虛表如下 " << std::endl;
Base1 base1;
std::cout << "base1物件的地址: " << (&base1) << std::endl;
std::cout << "base1物件中第一個指標的地址(不是內容): " << (&base1) << std::endl;
// &base1 是一個指向指標的指標
// 對它進行*操作, 得到了這個指標的內容, 即它指向的位置, 是一個虛表(虛表的內容也是一堆指標)
std::cout << "base1物件中第一個指標的內容: " << *reinterpret_cast<int**>(&base1) << std::endl;
std::cout << "base1虛擬函式表地址: " << *reinterpret_cast<int**>(&base1) << std::endl;
// 虛擬函式表地址, 也是一個指向指標的指標
// 對它進行*操作, 得到了這個指標的內容, 即它指向的位置, 是一個函式指標
std::cout << "base1虛擬函式表 — 第一個函式地址:" << (*(reinterpret_cast<int**>(*(reinterpret_cast<int**>(&base1))))) << std::endl;
pFun = reinterpret_cast<Fun>(* (reinterpret_cast<int**>(*(reinterpret_cast<int**>(&base1)))));
std::cout << "base1虛擬函式表 — 第一個函式內容:";
pFun(); // base1::f
std::cout << std::endl;
// 注意次數的偏移量, 32位偏移量是+1, 64位偏移量是+2
std::cout << "base1虛擬函式表 — 第二個函式地址:" << (*(reinterpret_cast<int**>(*(reinterpret_cast<int**>(&base1))+1*(sizeof(int*)/sizeof(int))))) << std::endl;
pFun = reinterpret_cast<Fun>(*(reinterpret_cast<int**>(*(reinterpret_cast<int**>(&base1)) + 1 * (sizeof(int*) / sizeof(int)))));
std::cout << "base1虛擬函式表 — 第二個函式內容:";
pFun(); // base1::g
std::cout << std::endl;
std::cout << "base1虛擬函式表 — 第三個函式地址:" << (*(reinterpret_cast<int**>(*(reinterpret_cast<int**>(&base1)) + 2 * (sizeof(int*) / sizeof(int))))) << std::endl;
pFun = reinterpret_cast<Fun>(*(reinterpret_cast<int**>(*(reinterpret_cast<int**>(&base1)) + 2 * (sizeof(int*) / sizeof(int)))));
std::cout << "base1虛擬函式表 — 第三個函式內容:";
pFun(); // base1::h
std::cout << std::endl;
std::cout << "base1虛擬函式表 — 第四個函式地址:" << (*(reinterpret_cast<int**>(*(reinterpret_cast<int**>(&base1)) + 3 * (sizeof(int*) / sizeof(int))))) << std::endl;
pFun = reinterpret_cast<Fun>(*(reinterpret_cast<int**>(*(reinterpret_cast<int**>(&base1)) + 3 * (sizeof(int*) / sizeof(int)))));
std::cout << "base1虛擬函式表 — 第四個函式內容:";
pFun(); // base1::i
std::cout << std::endl;
std::cout << "-------------------------------------------------------------------------------------- " << std::endl;
std::cout << "Base2的虛表如下 " << std::endl;
Base2 base2;
std::cout << "base2物件的地址: " << (&base2) << std::endl;
std::cout << "base2物件中第一個指標的地址(不是內容): " << (&base2) << std::endl;
// &base1 是一個指向指標的指標
// 對它進行*操作, 得到了這個指標的內容, 即它指向的位置, 是一個虛表(虛表的內容也是一堆指標)
std::cout << "base2物件中第一個指標的內容: " << *reinterpret_cast<int**>(&base2) << std::endl;
std::cout << "base2虛擬函式表地址: " << *reinterpret_cast<int**>(&base2) << std::endl;
// 虛擬函式表地址, 也是一個指向指標的指標
// 對它進行*操作, 得到了這個指標的內容, 即它指向的位置, 是一個函式指標
std::cout << "base2虛擬函式表 — 第一個函式地址:" << (*(reinterpret_cast<int**>(*(reinterpret_cast<int**>(&base2))))) << std::endl;
pFun = reinterpret_cast<Fun>(*(reinterpret_cast<int**>(*(reinterpret_cast<int**>(&base2)))));
std::cout << "base2虛擬函式表 — 第一個函式內容:";
pFun(); // base2::f
std::cout << std::endl;
std::cout << "base2虛擬函式表 — 第二個函式地址:" << (*(reinterpret_cast<int**>(*(reinterpret_cast<int**>(&base2)) + 1 * (sizeof(int*) / sizeof(int))))) << std::endl;
pFun = reinterpret_cast<Fun>(*(reinterpret_cast<int**>(*(reinterpret_cast<int**>(&base2)) + 1 * (sizeof(int*) / sizeof(int)))));
std::cout << "base2虛擬函式表 — 第二個函式內容:";
pFun(); // base1::g
std::cout << std::endl;
std::cout << "base2虛擬函式表 — 第三個函式地址:" << (*(reinterpret_cast<int**>(*(reinterpret_cast<int**>(&base2)) + 2 * (sizeof(int*) / sizeof(int))))) << std::endl;
pFun = reinterpret_cast<Fun>(*(reinterpret_cast<int**>(*(reinterpret_cast<int**>(&base2)) + 2 * (sizeof(int*) / sizeof(int)))));
std::cout << "base2虛擬函式表 — 第三個函式內容:";
pFun(); // base1::h
std::cout << std::endl;
std::cout << "base2虛擬函式表 — 第四個函式地址:" << (*(reinterpret_cast<int**>(*(reinterpret_cast<int**>(&base2)) + 3 * (sizeof(int*) / sizeof(int))))) << std::endl;
pFun = reinterpret_cast<Fun>(*(reinterpret_cast<int**>(*(reinterpret_cast<int**>(&base2)) + 3 * (sizeof(int*) / sizeof(int)))));
std::cout << "base2虛擬函式表 — 第四個函式內容:";
pFun(); // base1::i
std::cout << std::endl;
std::cout << "base2虛擬函式表 — 第五個函式地址:" << (*(reinterpret_cast<int**>(*(reinterpret_cast<int**>(&base2)) + 4 * (sizeof(int*) / sizeof(int))))) << std::endl;
pFun = reinterpret_cast<Fun>(*(reinterpret_cast<int**>(*(reinterpret_cast<int**>(&base2)) + 4 * (sizeof(int*) / sizeof(int)))));
std::cout << "base2虛擬函式表 — 第五個函式內容:";
pFun(); // base2::h1
std::cout << std::endl;
std::cout << "-------------------------------------------------------------------------------------- " << std::endl;
std::cout << "Derived的虛表如下 " << std::endl;
Derived Derived;
std::cout << "Derived物件的地址: " << (&Derived) << std::endl;
std::cout << "Derived物件中第一個指標的地址(不是內容): " << (&Derived) << std::endl;
// &base1 是一個指向指標的指標
// 對它進行*操作, 得到了這個指標的內容, 即它指向的位置, 是一個虛表(虛表的內容也是一堆指標)
std::cout << "Derived物件中第一個指標的內容: " << *reinterpret_cast<int**>(&Derived) << std::endl;
std::cout << "Derived虛擬函式表地址: " << *reinterpret_cast<int**>(&Derived) << std::endl;
// 虛擬函式表地址, 也是一個指向指標的指標
// 對它進行*操作, 得到了這個指標的內容, 即它指向的位置, 是一個函式指標
std::cout << "Derived虛擬函式表 — 第一個函式地址:" << (*(reinterpret_cast<int**>(*(reinterpret_cast<int**>(&Derived))))) << std::endl;
pFun = reinterpret_cast<Fun>(*(reinterpret_cast<int**>(*(reinterpret_cast<int**>(&Derived)))));
std::cout << "Derived虛擬函式表 — 第一個函式內容:";
pFun(); // base2::f
std::cout << std::endl;
std::cout << "Derived虛擬函式表 — 第二個函式地址:" << (*(reinterpret_cast<int**>(*(reinterpret_cast<int**>(&Derived)) + 1 * (sizeof(int*) / sizeof(int))))) << std::endl;
pFun = reinterpret_cast<Fun>(*(reinterpret_cast<int**>(*(reinterpret_cast<int**>(&Derived)) + 1 * (sizeof(int*) / sizeof(int)))));
std::cout << "Derived虛擬函式表 — 第二個函式內容:";
pFun(); // Derived::g
std::cout << std::endl;
std::cout << "Derived虛擬函式表 — 第三個函式地址:" << (*(reinterpret_cast<int**>(*(reinterpret_cast<int**>(&Derived)) + 2 * (sizeof(int*) / sizeof(int))))) << std::endl;
pFun = reinterpret_cast<Fun>(*(reinterpret_cast<int**>(*(reinterpret_cast<int**>(&Derived)) + 2 * (sizeof(int*) / sizeof(int)))));
std::cout << "Derived虛擬函式表 — 第三個函式內容:";
pFun(); // base1::h
std::cout << std::endl;
std::cout << "Derived虛擬函式表 — 第四個函式地址:" << (*(reinterpret_cast<int**>(*(reinterpret_cast<int**>(&Derived)) + 3 * (sizeof(int*) / sizeof(int))))) << std::endl;
pFun = reinterpret_cast<Fun>(*(reinterpret_cast<int**>(*(reinterpret_cast<int**>(&Derived)) + 3 * (sizeof(int*) / sizeof(int)))));
std::cout << "Derived虛擬函式表 — 第四個函式內容:";
pFun(); // base1::i
std::cout << std::endl;
std::cout << "Derived虛擬函式表 — 第五個函式地址:" << (*(reinterpret_cast<int**>(*(reinterpret_cast<int**>(&Derived)) + 4 * (sizeof(int*) / sizeof(int))))) << std::endl;
pFun = reinterpret_cast<Fun>(*(reinterpret_cast<int**>(*(reinterpret_cast<int**>(&Derived)) + 4 * (sizeof(int*) / sizeof(int)))));
std::cout << "Derived虛擬函式表 — 第五個函式內容:";
pFun(); // base2::h1
std::cout << std::endl;
std::cout << "Derived虛擬函式表 — 第六個函式地址:" << (*(reinterpret_cast<int**>(*(reinterpret_cast<int**>(&Derived)) + 5 * (sizeof(int*) / sizeof(int))))) << std::endl;
pFun = reinterpret_cast<Fun>(*(reinterpret_cast<int**>(*(reinterpret_cast<int**>(&Derived)) + 5 * (sizeof(int*) / sizeof(int)))));
std::cout << "Derived虛擬函式表 — 第六個函式內容:";
pFun(); // Derived::i1
std::cout << std::endl;
return 0;
}