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的__vfptr對象的地址, 和控制台打印的內容是相同的

 

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/博客圖床/C%2B%2B 鏈��%繼承下的虛函數列表/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;
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM