C++ 虛繼承以及內存佈局


本篇源碼部分來自clang,部分來自《C++程序設計語言 4th》的21章。

本篇是在學/複習虛繼承的相關概念,發現這個一般用不到的概念還是有很多東西沒有理解了的。

什麼時候用到虛繼承

有的時候,在類設計上,將數據、接口放在了基類中,就可能會產生了菱形繼承結構。比如:

class Storable {
public:
    Storable(const string&);
    virtual void read() = 0;
    virtual void write() = 0;
    virtual Storable();
    
protected:
    string file_name;
    Storable(Storable&) = delete;
    Storable operator=(Storable&) = delete;
};

class Transmitter : public Storable {
public:
    void write() override;
    // ...
};

class Receiver : public virtual Storable {
public:
    void read() override;
    // ...
};

class Radio : public Transmitter, public Receiver {
public:
    void write() override;
    // ...
};

除了數據的共享之外,還有接口的重用。

那麼,菱形繼承有什麼問題呢?答:數據和接口都會有兩份。數據會佔用內存,接口方面最終的派生類會搞不清楚到底使用哪個基類的已有實現,編譯器就會報錯。還有後文中提到的基類與派生類之間的類型轉化問題。解決編譯錯誤的方法就是使用虛繼承。

C++標準庫中的IO就用到了虛繼承:

class _LIBCPP_TYPE_VIS ios_base
{
    ...
};

template <class _CharT, class _Traits>
class _LIBCPP_TEMPLATE_VIS basic_ios
    : public ios_base
{
    ...
};

template <class _CharT, class _Traits>
class _LIBCPP_TEMPLATE_VIS basic_istream
    : virtual public basic_ios<_CharT, _Traits>
{
    ...
};

template <class _CharT, class _Traits>
class _LIBCPP_TEMPLATE_VIS basic_ostream
    : virtual public basic_ios<_CharT, _Traits>
{
    ...
}

template <class _CharT, class _Traits>
class _LIBCPP_TEMPLATE_VIS basic_iostream
    : public basic_istream<_CharT, _Traits>,
      public basic_ostream<_CharT, _Traits>
{
    ...
};

繼承結構如下圖:

graph TB
ios_base-->basic_ios
basic_ios-->basic_istream
basic_ios-->basic_ostream
basic_istream-->basic_iostream
basic_ostream-->basic_iostream

其中各個類常見的名字是:

// char版本,還有一個wchar_t版本
typedef basic_istream<char>          istream;
typedef basic_ostream<char>          ostream;
typedef basic_iostream<char>         iostream;

// 而istream和ostream分別是:
extern _LIBCPP_FUNC_VIS istream cin;
extern _LIBCPP_FUNC_VIS ostream cout;
// 還有
extern _LIBCPP_FUNC_VIS ostream cerr;
extern _LIBCPP_FUNC_VIS ostream clog;

我們很少使用basic_iostream,也就是iostream。

虛基類的初始化

虛基類的初始化任務最終是由派生類完成的。虛基類永遠被認爲是其派生類的直接基類。

struct V {
    V(int);
};

struct A {
    A();
};

struct B : virtual V, virtual A {
    B() : V{1} {/*...*/}    // 必須顯式初始化虛基類V
};

class C : virtual V {
    C(int i) : V{i} { ... } // 必須顯式初始化虛基類V
};

class D : virtual public V, virtual public C {
    // 從B,C隱式地繼承虛基類V
    // 從B隱式地繼承虛基類A
public: 
    D() { /*...*/}                      // error:C,V沒有默認構造函數
    D(int i) : C{i} { ... }             // error: V沒有默認構造函數
    D(int i, int j) : V{i}, C{j}{...}   // OK
};

上面D必須給V提供一個初始化,就像是D直接繼承(不論是不是直接繼承)自V一樣。而且在構造D的時候,V的構造函數調用是自D中調用的,直接忽略B,C的構造函數,且與一般繼承結構類似,先構造基類,這裏是先構造虛基類。

虛基類的成員調用

一個虛基類的派生類在實現繼承的接口時,不知道未來是否會有其他派生類實現該虛基類。在真正實現自身接口的時候,可以通過添加限定符直接調用虛基類的接口,而且這個過程沒有用到虛調用機制。

菱形繼承的虛繼承在使用的時候,不能從派生類隱式轉化爲虛基類,因爲搞不清楚是哪條派生路徑,這決定了使用那個派生路徑的具體實現,所以編譯器需要搞清楚,從而出現二義性。相反地,從虛基類向派生類轉化也是不行的。

如果兩個派生自同一個基類的類都實現了同一個接口,但是這這兩個派生類沒有接口覆蓋關係,那麼這兩個派生類作爲基類派生一個新的類的時候,編譯器會認爲是一個錯誤,除非最終的派生類覆蓋這個接口。

還有,虛繼承會產生一個相對於常規繼承而言比較“怪異”的調用行爲,參考下面:

// 一個被描述爲 "delegate to a sister class" via virtual inheritance 的繼承結構

class Base {
public:
  virtual void foo() = 0;
  virtual void bar() = 0;
};

class Der1 : public virtual Base {
public:
  virtual void foo();
};

void Der1::foo()
{ 
    bar(); 
}

class Der2 : public virtual Base {
public:
  virtual void bar(){
        std::cout << "in Der2::bar()" << std::endl;
    }
};

class Join : public Der1, public Der2 {
public:
  // ...
};

int main()
{
  Join* p1 = new Join();
  Der1* p2 = p1;
  Base* p3 = p1;
  p1->foo();
  p2->foo();
  p3->foo();
}

// 輸出:
in Der2::bar()
in Der2::bar()
in Der2::bar()

Der1::foo() 調用 this->bar(),最終調用的是Der2::bar();這個叫做“cross delegation”,可用來做一些特殊的自定義多態行爲。這個是跟虛繼承的內存結構相關的。

幾種虛繼承

下面兩種繼承結構,前者使用虛繼承,後者不使用虛繼承。

class BB_ival_slider : public virtual Ival_slider, protected BBSlider {};
class Popup_ival_slider : public virtual Ival_slider {};
class BB_popup_ival_slider : public virtual Popup_ival_slider, protected BB_ival_slider {};


class BB_ival_slider : public Ival_slider, protected BBSlider {};
class Popup_ival_slider : public Ival_slider {};
class BB_popup_ival_slider : public Popup_ival_slider, protected BB_ival_slider {};

對比兩種方案,虛基類的方案在派生類隱式轉換爲虛基類,以及相反的轉換時有二義性問題;而後者沒有問題。

另外,非虛基類的方案生成的對象空間佔用較小(不需要數據結構來支持共享)。參考reference 1,可以看到虛繼承在實現的時候,相比一般的繼承,出現了一個新的輔助工具vbase_offset,用來記錄虛函數具體使用哪一個入口。

虛繼承與訪問控制的關係

virtual可以與public、protected、private配合出現,二者組合順序無所謂virtual public == public virtual。

幾個問題

那麼,std::iostream如何使用呢?好像很少見到使用的場景。一般單獨使用cin和cout。

看一個例子,觀察虛繼承對象內存結構

寫一個示例代碼test.cpp,使用clang查看內存結構:

查看下面的數據的時候,要注意:對於堆來講,生長方向是向上的,也就是向着內存地址增加的方向;對於棧來講,它的生長方式是向下的,是向着內存地址減小的方向增長。

struct A
{
    int ax;
    virtual void f0() {}
    virtual void bar() {}
};

struct B : virtual public A
{
    int bx;
    void f0() {}
};

struct C : virtual public A
{
    int cx;
    void f0() {}
};

struct D : public B, public C
{
    int dx;
    void f0() {}
};

int main()
{
    D d;
    return 0;
}

使用命令:clang -cc1 -stdlib=libc++ -fdump-record-layouts -fdump-vtable-layouts -emit-llvm ./test.cpp

*** Dumping AST Record Layout
         0 | struct A
         0 |   (A vtable pointer)
         8 |   int ax
           | [sizeof=16, dsize=12, align=8,
           |  nvsize=12, nvalign=8]

*** Dumping AST Record Layout
         0 | struct B
         0 |   (B vtable pointer)
         8 |   int bx
        16 |   struct A (virtual base)
        16 |     (A vtable pointer)
        24 |     int ax
           | [sizeof=32, dsize=28, align=8,
           |  nvsize=12, nvalign=8]

*** Dumping AST Record Layout
         0 | struct C
         0 |   (C vtable pointer)
         8 |   int cx
        16 |   struct A (virtual base)
        16 |     (A vtable pointer)
        24 |     int ax
           | [sizeof=32, dsize=28, align=8,
           |  nvsize=12, nvalign=8]

*** Dumping AST Record Layout
         0 | struct D
         0 |   struct B (primary base)
         0 |     (B vtable pointer)
         8 |     int bx
        16 |   struct C (base)
        16 |     (C vtable pointer)
        24 |     int cx
        28 |   int dx
        32 |   struct A (virtual base)
        32 |     (A vtable pointer)
        40 |     int ax
           | [sizeof=48, dsize=44, align=8,
           |  nvsize=32, nvalign=8]

*** Dumping IRgen Record Layout
Record: CXXRecordDecl 0x7f8398078700 <./test.cpp:1:1, line:6:1> line:1:8 referenced struct A definition
|-DefinitionData polymorphic
| |-DefaultConstructor exists non_trivial
| |-CopyConstructor simple non_trivial has_const_param implicit_has_const_param
| |-MoveConstructor exists simple non_trivial needs_implicit
| |-CopyAssignment non_trivial has_const_param implicit_has_const_param
| |-MoveAssignment exists simple non_trivial needs_implicit
| `-Destructor simple irrelevant trivial
|-CXXRecordDecl 0x7f8398078818 <col:1, col:8> col:8 implicit struct A
|-FieldDecl 0x7f83980788c0 <line:3:5, col:9> col:9 ax 'int'
|-CXXMethodDecl 0x7f8398078958 <line:4:5, col:24> col:18 f0 'void ()' virtual
| `-CompoundStmt 0x7f8398078db8 <col:23, col:24>
|-CXXMethodDecl 0x7f8398078a18 <line:5:5, col:25> col:18 bar 'void ()' virtual
| `-CompoundStmt 0x7f8398078dc8 <col:24, col:25>
|-CXXMethodDecl 0x7f8398078b18 <line:1:8> col:8 implicit operator= 'A &(const A &)' inline default noexcept-unevaluated 0x7f8398078b18
| `-ParmVarDecl 0x7f8398078c28 <col:8> col:8 'const A &'
|-CXXDestructorDecl 0x7f8398078cb0 <col:8> col:8 implicit ~A 'void ()' inline default trivial noexcept-unevaluated 0x7f8398078cb0
|-CXXConstructorDecl 0x7f83980a3ef8 <col:8> col:8 implicit used A 'void () throw()' inline default
| `-CompoundStmt 0x7f83980a4278 <col:8>
`-CXXConstructorDecl 0x7f83980a40f0 <col:8> col:8 implicit A 'void (const A &)' inline default noexcept-unevaluated 0x7f83980a40f0
  `-ParmVarDecl 0x7f83980a4208 <col:8> col:8 'const A &'

Layout: <CGRecordLayout
  LLVMType:%struct.A = type <{ i32 (...)**, i32, [4 x i8] }>
  NonVirtualBaseLLVMType:%struct.A.base = type <{ i32 (...)**, i32 }>
  IsZeroInitializable:1
  BitFields:[
]>

*** Dumping IRgen Record Layout
Record: CXXRecordDecl 0x7f8398078dd8 <./test.cpp:8:1, line:12:1> line:8:8 referenced struct B definition
|-DefinitionData polymorphic
| |-DefaultConstructor exists non_trivial
| |-CopyConstructor simple non_trivial has_const_param implicit_has_const_param
| |-MoveConstructor exists simple non_trivial needs_implicit
| |-CopyAssignment non_trivial has_const_param implicit_has_const_param
| |-MoveAssignment exists simple non_trivial needs_implicit
| `-Destructor simple irrelevant trivial
|-virtual public 'A'
|-CXXRecordDecl 0x7f8398078f50 <col:1, col:8> col:8 implicit struct B
|-FieldDecl 0x7f8398078ff8 <line:10:5, col:9> col:9 bx 'int'
|-CXXMethodDecl 0x7f8398079068 <line:11:5, col:16> col:10 f0 'void ()'
| |-Overrides: [ 0x7f8398078958 A::f0 'void ()' ]
| `-CompoundStmt 0x7f8398079408 <col:15, col:16>
|-CXXMethodDecl 0x7f8398079168 <line:8:8> col:8 implicit operator= 'B &(const B &)' inline default noexcept-unevaluated 0x7f8398079168
| `-ParmVarDecl 0x7f8398079278 <col:8> col:8 'const B &'
|-CXXDestructorDecl 0x7f8398079300 <col:8> col:8 implicit ~B 'void ()' inline default trivial noexcept-unevaluated 0x7f8398079300
|-CXXConstructorDecl 0x7f83980a3e00 <col:8> col:8 implicit used B 'void () throw()' inline default
| |-CXXCtorInitializer 'A'
| | `-CXXConstructExpr 0x7f83980a4468 <col:8> 'A' 'void () throw()'
| `-CompoundStmt 0x7f83980a44d0 <col:8>
`-CXXConstructorDecl 0x7f83980a42e8 <col:8> col:8 implicit B 'void (const B &)' inline default noexcept-unevaluated 0x7f83980a42e8
  `-ParmVarDecl 0x7f83980a43f8 <col:8> col:8 'const B &'

Layout: <CGRecordLayout
  LLVMType:%struct.B = type <{ i32 (...)**, i32, [4 x i8], %struct.A.base, [4 x i8] }>
  NonVirtualBaseLLVMType:%struct.B.base = type <{ i32 (...)**, i32 }>
  IsZeroInitializable:1
  BitFields:[
]>

*** Dumping IRgen Record Layout
Record: CXXRecordDecl 0x7f8398079418 <./test.cpp:14:1, line:18:1> line:14:8 referenced struct C definition
|-DefinitionData polymorphic
| |-DefaultConstructor exists non_trivial
| |-CopyConstructor simple non_trivial has_const_param implicit_has_const_param
| |-MoveConstructor exists simple non_trivial needs_implicit
| |-CopyAssignment non_trivial has_const_param implicit_has_const_param
| |-MoveAssignment exists simple non_trivial needs_implicit
| `-Destructor simple irrelevant trivial
|-virtual public 'A'
|-CXXRecordDecl 0x7f83980a2200 <col:1, col:8> col:8 implicit struct C
|-FieldDecl 0x7f83980a22a8 <line:16:5, col:9> col:9 cx 'int'
|-CXXMethodDecl 0x7f83980a2318 <line:17:5, col:16> col:10 f0 'void ()'
| |-Overrides: [ 0x7f8398078958 A::f0 'void ()' ]
| `-CompoundStmt 0x7f83980a26b8 <col:15, col:16>
|-CXXMethodDecl 0x7f83980a2418 <line:14:8> col:8 implicit operator= 'C &(const C &)' inline default noexcept-unevaluated 0x7f83980a2418
| `-ParmVarDecl 0x7f83980a2528 <col:8> col:8 'const C &'
|-CXXDestructorDecl 0x7f83980a25b0 <col:8> col:8 implicit ~C 'void ()' inline default trivial noexcept-unevaluated 0x7f83980a25b0
|-CXXConstructorDecl 0x7f83980a4010 <col:8> col:8 implicit used C 'void () throw()' inline default
| |-CXXCtorInitializer 'A'
| | `-CXXConstructExpr 0x7f83980a46c8 <col:8> 'A' 'void () throw()'
| `-CompoundStmt 0x7f83980a4730 <col:8>
`-CXXConstructorDecl 0x7f83980a4540 <col:8> col:8 implicit C 'void (const C &)' inline default noexcept-unevaluated 0x7f83980a4540
  `-ParmVarDecl 0x7f83980a4658 <col:8> col:8 'const C &'

Layout: <CGRecordLayout
  LLVMType:%struct.C = type <{ i32 (...)**, i32, [4 x i8], %struct.A.base, [4 x i8] }>
  NonVirtualBaseLLVMType:%struct.C.base = type <{ i32 (...)**, i32 }>
  IsZeroInitializable:1
  BitFields:[
]>

*** Dumping IRgen Record Layout
Record: CXXRecordDecl 0x7f83980a26c8 <./test.cpp:20:1, line:24:1> line:20:8 referenced struct D definition
|-DefinitionData polymorphic
| |-DefaultConstructor exists non_trivial
| |-CopyConstructor simple non_trivial has_const_param implicit_has_const_param
| |-MoveConstructor exists simple non_trivial needs_implicit
| |-CopyAssignment non_trivial has_const_param implicit_has_const_param
| |-MoveAssignment exists simple non_trivial needs_implicit
| `-Destructor simple irrelevant trivial
|-public 'B'
|-public 'C'
|-CXXRecordDecl 0x7f83980a2880 <col:1, col:8> col:8 implicit struct D
|-FieldDecl 0x7f83980a2928 <line:22:5, col:9> col:9 dx 'int'
|-CXXMethodDecl 0x7f83980a2998 <line:23:5, col:16> col:10 f0 'void ()'
| |-Overrides: [ 0x7f8398079068 B::f0 'void ()', 0x7f83980a2318 C::f0 'void ()' ]
| `-CompoundStmt 0x7f83980a2d38 <col:15, col:16>
|-CXXMethodDecl 0x7f83980a2a98 <line:20:8> col:8 implicit operator= 'D &(const D &)' inline default noexcept-unevaluated 0x7f83980a2a98
| `-ParmVarDecl 0x7f83980a2ba8 <col:8> col:8 'const D &'
|-CXXDestructorDecl 0x7f83980a2c30 <col:8> col:8 implicit ~D 'void ()' inline default trivial noexcept-unevaluated 0x7f83980a2c30
|-CXXConstructorDecl 0x7f83980a2f40 <col:8> col:8 implicit used D 'void () throw()' inline default
| |-CXXCtorInitializer 'A'
| | `-CXXConstructExpr 0x7f83980a4288 <col:8> 'A' 'void () throw()'
| |-CXXCtorInitializer 'B'
| | `-CXXConstructExpr 0x7f83980a44e0 <col:8> 'B' 'void () throw()'
| |-CXXCtorInitializer 'C'
| | `-CXXConstructExpr 0x7f83980a4740 <col:8> 'C' 'void () throw()'
| `-CompoundStmt 0x7f83980a47b8 <col:8>
`-CXXConstructorDecl 0x7f83980a3020 <col:8> col:8 implicit D 'void (const D &)' inline default noexcept-unevaluated 0x7f83980a3020
  `-ParmVarDecl 0x7f83980a3138 <col:8> col:8 'const D &'

Layout: <CGRecordLayout
  LLVMType:%struct.D = type { %struct.B.base, [4 x i8], %struct.C.base, i32, %struct.A.base, [4 x i8] }
  NonVirtualBaseLLVMType:%struct.D.base = type { %struct.B.base, [4 x i8], %struct.C.base, i32 }
  IsZeroInitializable:1
  BitFields:[
]>
Original map
 void D::f0() -> void B::f0()
Vtable for 'D' (14 entries).
   0 | vbase_offset (32)
   1 | offset_to_top (0)
   2 | D RTTI
       -- (B, 0) vtable address --
       -- (D, 0) vtable address --
   3 | void D::f0()
   4 | vbase_offset (16)
   5 | offset_to_top (-16)
   6 | D RTTI
       -- (C, 16) vtable address --
   7 | void D::f0()
       [this adjustment: -16 non-virtual] method: void C::f0()
   8 | vcall_offset (0)
   9 | vcall_offset (-32)
  10 | offset_to_top (-32)
  11 | D RTTI
       -- (A, 32) vtable address --
  12 | void D::f0()
       [this adjustment: 0 non-virtual, -24 vcall offset offset] method: void A::f0()
  13 | void A::bar()

Virtual base offset offsets for 'D' (1 entry).
   A | -24

Thunks for 'void D::f0()' (2 entries).
   0 | this adjustment: -16 non-virtual
   1 | this adjustment: 0 non-virtual, -24 vcall offset offset

VTable indices for 'D' (1 entries).
   0 | void D::f0()

Original map
 void D::f0() -> void B::f0()
Construction vtable for ('B', 0) in 'D' (10 entries).
   0 | vbase_offset (32)
   1 | offset_to_top (0)
   2 | B RTTI
       -- (B, 0) vtable address --
   3 | void B::f0()
   4 | vcall_offset (0)
   5 | vcall_offset (-32)
   6 | offset_to_top (-32)
   7 | B RTTI
       -- (A, 32) vtable address --
   8 | void B::f0()
       [this adjustment: 0 non-virtual, -24 vcall offset offset] method: void A::f0()
   9 | void A::bar()

Original map
 void D::f0() -> void B::f0()
Construction vtable for ('C', 16) in 'D' (10 entries).
   0 | vbase_offset (16)
   1 | offset_to_top (0)
   2 | C RTTI
       -- (C, 16) vtable address --
   3 | void C::f0()
   4 | vcall_offset (0)
   5 | vcall_offset (-16)
   6 | offset_to_top (-16)
   7 | C RTTI
       -- (A, 32) vtable address --
   8 | void C::f0()
       [this adjustment: 0 non-virtual, -24 vcall offset offset] method: void A::f0()
   9 | void A::bar()

Original map
 void D::f0() -> void B::f0()
Vtable for 'A' (4 entries).
   0 | offset_to_top (0)
   1 | A RTTI
       -- (A, 0) vtable address --
   2 | void A::f0()
   3 | void A::bar()

VTable indices for 'A' (2 entries).
   0 | void A::f0()
   1 | void A::bar()

Original map
 void D::f0() -> void B::f0()
Vtable for 'B' (10 entries).
   0 | vbase_offset (16)
   1 | offset_to_top (0)
   2 | B RTTI
       -- (B, 0) vtable address --
   3 | void B::f0()
   4 | vcall_offset (0)
   5 | vcall_offset (-16)
   6 | offset_to_top (-16)
   7 | B RTTI
       -- (A, 16) vtable address --
   8 | void B::f0()
       [this adjustment: 0 non-virtual, -24 vcall offset offset] method: void A::f0()
   9 | void A::bar()

Virtual base offset offsets for 'B' (1 entry).
   A | -24

Thunks for 'void B::f0()' (1 entry).
   0 | this adjustment: 0 non-virtual, -24 vcall offset offset

VTable indices for 'B' (1 entries).
   0 | void B::f0()

Original map
 void D::f0() -> void B::f0()
Vtable for 'C' (10 entries).
   0 | vbase_offset (16)
   1 | offset_to_top (0)
   2 | C RTTI
       -- (C, 0) vtable address --
   3 | void C::f0()
   4 | vcall_offset (0)
   5 | vcall_offset (-16)
   6 | offset_to_top (-16)
   7 | C RTTI
       -- (A, 16) vtable address --
   8 | void C::f0()
       [this adjustment: 0 non-virtual, -24 vcall offset offset] method: void A::f0()
   9 | void A::bar()

Virtual base offset offsets for 'C' (1 entry).
   A | -24

Thunks for 'void C::f0()' (1 entry).
   0 | this adjustment: 0 non-virtual, -24 vcall offset offset

VTable indices for 'C' (1 entries).
   0 | void C::f0()

另一個例子:

class Base
{
public:
    virtual void foo() = 0;
    virtual void bar() = 0;
};

class Der1 : public virtual Base
{
public:
    virtual void foo();
};

void Der1::foo()
{
    bar();
}

class Der2 : public virtual Base
{
public:
    virtual void bar()
    {
    }
};

class Join : public Der1, public Der2
{
public:
    // ...
};

int main()
{
    Join *p1 = new Join();
    return 0;
}

使用命令編譯:clang -cc1 -stdlib=libc++ -fdump-record-layouts -fdump-vtable-layouts -emit-llvm ./test.cpp

*** Dumping AST Record Layout
         0 | class Base
         0 |   (Base vtable pointer)
           | [sizeof=8, dsize=8, align=8,
           |  nvsize=8, nvalign=8]

*** Dumping AST Record Layout
         0 | class Der1
         0 |   class Base (primary virtual base)
         0 |     (Base vtable pointer)
           | [sizeof=8, dsize=8, align=8,
           |  nvsize=8, nvalign=8]

*** Dumping IRgen Record Layout
Record: CXXRecordDecl 0x7ff083808300 <./test.cpp:1:1, line:6:1> line:1:7 referenced class Base definition
|-DefinitionData polymorphic abstract literal has_constexpr_non_copy_move_ctor can_const_default_init
| |-DefaultConstructor exists non_trivial constexpr needs_implicit defaulted_is_constexpr
| |-CopyConstructor simple non_trivial has_const_param needs_implicit implicit_has_const_param
| |-MoveConstructor exists simple non_trivial needs_implicit
| |-CopyAssignment non_trivial has_const_param implicit_has_const_param
| |-MoveAssignment exists simple non_trivial needs_implicit
| `-Destructor simple irrelevant trivial
|-CXXRecordDecl 0x7ff083808418 <col:1, col:7> col:7 implicit class Base
|-AccessSpecDecl 0x7ff0838084a8 <line:3:1, col:7> col:1 public
|-CXXMethodDecl 0x7ff083808518 <line:4:5, col:26> col:18 foo 'void ()' virtual pure
|-CXXMethodDecl 0x7ff0838085d8 <line:5:5, col:26> col:18 referenced bar 'void ()' virtual pure
|-CXXMethodDecl 0x7ff0838086d8 <line:1:7> col:7 implicit operator= 'Base &(const Base &)' inline default noexcept-unevaluated 0x7ff0838086d8
| `-ParmVarDecl 0x7ff0838087e8 <col:7> col:7 'const Base &'
`-CXXDestructorDecl 0x7ff083808870 <col:7> col:7 implicit ~Base 'void ()' inline default trivial noexcept-unevaluated 0x7ff083808870

Layout: <CGRecordLayout
  LLVMType:%class.Base = type { i32 (...)** }
  NonVirtualBaseLLVMType:%class.Base = type { i32 (...)** }
  IsZeroInitializable:1
  BitFields:[
]>

*** Dumping IRgen Record Layout
Record: CXXRecordDecl 0x7ff083808950 <./test.cpp:8:1, line:12:1> line:8:7 class Der1 definition
|-DefinitionData polymorphic abstract can_const_default_init
| |-DefaultConstructor exists non_trivial needs_implicit
| |-CopyConstructor simple non_trivial has_const_param needs_implicit implicit_has_const_param
| |-MoveConstructor exists simple non_trivial needs_implicit
| |-CopyAssignment non_trivial has_const_param implicit_has_const_param
| |-MoveAssignment exists simple non_trivial needs_implicit
| `-Destructor simple irrelevant trivial
|-virtual public 'Base'
|-CXXRecordDecl 0x7ff083808ac0 <col:1, col:7> col:7 implicit class Der1
|-AccessSpecDecl 0x7ff083808b50 <line:10:1, col:7> col:1 public
|-CXXMethodDecl 0x7ff083808b98 <line:11:5, col:22> col:18 foo 'void ()' virtual
| `-Overrides: [ 0x7ff083808518 Base::foo 'void ()' ]
|-CXXMethodDecl 0x7ff083808c98 <line:8:7> col:7 implicit operator= 'Der1 &(const Der1 &)' inline default noexcept-unevaluated 0x7ff083808c98
| `-ParmVarDecl 0x7ff083808da8 <col:7> col:7 'const Der1 &'
`-CXXDestructorDecl 0x7ff083808e30 <col:7> col:7 implicit ~Der1 'void ()' inline default trivial noexcept-unevaluated 0x7ff083808e30

Layout: <CGRecordLayout
  LLVMType:%class.Der1 = type { %class.Base }
  NonVirtualBaseLLVMType:%class.Der1 = type { %class.Base }
  IsZeroInitializable:1
  BitFields:[
]>
Original map
Vtable for 'Base' (4 entries).
   0 | offset_to_top (0)
   1 | Base RTTI
       -- (Base, 0) vtable address --
   2 | void Base::foo() [pure]
   3 | void Base::bar() [pure]

VTable indices for 'Base' (2 entries).
   0 | void Base::foo()
   1 | void Base::bar()

Original map
 void Der1::foo() -> void Base::foo()
Vtable for 'Der1' (7 entries).
   0 | vbase_offset (0)
   1 | vcall_offset (0)
   2 | vcall_offset (0)
   3 | offset_to_top (0)
   4 | Der1 RTTI
       -- (Base, 0) vtable address --
       -- (Der1, 0) vtable address --
   5 | void Der1::foo()
   6 | void Base::bar() [pure]

Virtual base offset offsets for 'Der1' (1 entry).
   Base | -40

Thunks for 'void Der1::foo()' (1 entry).
   0 | this adjustment: 0 non-virtual, -24 vcall offset offset

VTable indices for 'Der1' (1 entries).
   0 | void Der1::foo()


*** Dumping AST Record Layout
         0 | class Der2
         0 |   class Base (primary virtual base)
         0 |     (Base vtable pointer)
           | [sizeof=8, dsize=8, align=8,
           |  nvsize=8, nvalign=8]

*** Dumping AST Record Layout
         0 | class Join
         0 |   class Der1 (primary base)
         8 |   class Der2 (base)
         0 |   class Base (virtual base)
         0 |     (Base vtable pointer)
           | [sizeof=16, dsize=16, align=8,
           |  nvsize=16, nvalign=8]

*** Dumping IRgen Record Layout
Record: CXXRecordDecl 0x7ff083836590 <./test.cpp:19:1, line:25:1> line:19:7 referenced class Der2 definition
|-DefinitionData polymorphic abstract can_const_default_init
| |-DefaultConstructor exists non_trivial
| |-CopyConstructor simple non_trivial has_const_param implicit_has_const_param
| |-MoveConstructor exists simple non_trivial needs_implicit
| |-CopyAssignment non_trivial has_const_param implicit_has_const_param
| |-MoveAssignment exists simple non_trivial needs_implicit
| `-Destructor simple irrelevant trivial
|-virtual public 'Base'
|-CXXRecordDecl 0x7ff083836700 <col:1, col:7> col:7 implicit class Der2
|-AccessSpecDecl 0x7ff083836790 <line:21:1, col:7> col:1 public
|-CXXMethodDecl 0x7ff0838367d8 <line:22:5, line:24:5> line:22:18 bar 'void ()' virtual
| |-Overrides: [ 0x7ff0838085d8 Base::bar 'void ()' ]
| `-CompoundStmt 0x7ff083836b78 <line:23:5, line:24:5>
|-CXXMethodDecl 0x7ff0838368d8 <line:19:7> col:7 implicit operator= 'Der2 &(const Der2 &)' inline default noexcept-unevaluated 0x7ff0838368d8
| `-ParmVarDecl 0x7ff0838369e8 <col:7> col:7 'const Der2 &'
|-CXXDestructorDecl 0x7ff083836a70 <col:7> col:7 implicit ~Der2 'void ()' inline default trivial noexcept-unevaluated 0x7ff083836a70
|-CXXConstructorDecl 0x7ff08300b350 <col:7> col:7 implicit used Der2 'void () throw()' inline default
| `-CompoundStmt 0x7ff08287b898 <col:7>
`-CXXConstructorDecl 0x7ff08287b718 <col:7> col:7 implicit Der2 'void (const Der2 &)' inline default noexcept-unevaluated 0x7ff08287b718
  `-ParmVarDecl 0x7ff08287b828 <col:7> col:7 'const Der2 &'

Layout: <CGRecordLayout
  LLVMType:%class.Der2 = type { %class.Base }
  NonVirtualBaseLLVMType:%class.Der2 = type { %class.Base }
  IsZeroInitializable:1
  BitFields:[
]>

*** Dumping IRgen Record Layout
Record: CXXRecordDecl 0x7ff083836b88 <./test.cpp:27:1, line:31:1> line:27:7 referenced class Join definition
|-DefinitionData polymorphic can_const_default_init
| |-DefaultConstructor exists non_trivial
| |-CopyConstructor simple non_trivial has_const_param implicit_has_const_param
| |-MoveConstructor exists simple non_trivial needs_implicit
| |-CopyAssignment non_trivial has_const_param implicit_has_const_param
| |-MoveAssignment exists simple non_trivial needs_implicit
| `-Destructor simple irrelevant trivial
|-public 'Der1'
|-public 'Der2'
|-CXXRecordDecl 0x7ff083836d40 <col:1, col:7> col:7 implicit class Join
|-AccessSpecDecl 0x7ff083836dd0 <line:29:1, col:7> col:1 public
|-CXXMethodDecl 0x7ff083836e58 <line:27:7> col:7 implicit operator= 'Join &(const Join &)' inline default noexcept-unevaluated 0x7ff083836e58
| `-ParmVarDecl 0x7ff083836f68 <col:7> col:7 'const Join &'
|-CXXDestructorDecl 0x7ff083836ff0 <col:7> col:7 implicit ~Join 'void ()' inline default trivial noexcept-unevaluated 0x7ff083836ff0
|-CXXConstructorDecl 0x7ff08300afb8 <col:7> col:7 implicit used Join 'void () throw()' inline default
| |-CXXCtorInitializer 'Base'
| | `-CXXConstructExpr 0x7ff08300b6b8 <col:7> 'Base' 'void () throw()'
| |-CXXCtorInitializer 'Der1'
| | `-CXXConstructExpr 0x7ff08287b6b8 <col:7> 'Der1' 'void () throw()'
| |-CXXCtorInitializer 'Der2'
| | `-CXXConstructExpr 0x7ff08287b8a8 <col:7> 'Der2' 'void () throw()'
| `-CompoundStmt 0x7ff08287b920 <col:7>
`-CXXConstructorDecl 0x7ff08300b090 <col:7> col:7 implicit Join 'void (const Join &)' inline default noexcept-unevaluated 0x7ff08300b090
  `-ParmVarDecl 0x7ff08300b1a8 <col:7> col:7 'const Join &'

Layout: <CGRecordLayout
  LLVMType:%class.Join = type { %class.Der1, %class.Der2 }
  NonVirtualBaseLLVMType:%class.Join = type { %class.Der1, %class.Der2 }
  IsZeroInitializable:1
  BitFields:[
]>
Original map
 void Der2::bar() -> void Base::bar()
 void Der1::foo() -> void Base::foo()
Vtable for 'Join' (14 entries).
   0 | vbase_offset (0)
   1 | vcall_offset (8)
   2 | vcall_offset (0)
   3 | offset_to_top (0)
   4 | Join RTTI
       -- (Base, 0) vtable address --
       -- (Der1, 0) vtable address --
       -- (Join, 0) vtable address --
   5 | void Der1::foo()
   6 | void Der2::bar()
       [this adjustment: 0 non-virtual, -32 vcall offset offset] method: void Base::bar()
   7 | vbase_offset (-8)
   8 | vcall_offset (0)
   9 | vcall_offset (-8)
  10 | offset_to_top (-8)
  11 | Join RTTI
       -- (Der2, 8) vtable address --
  12 | [unused] void Der1::foo()
  13 | void Der2::bar()

Virtual base offset offsets for 'Join' (1 entry).
   Base | -40


Original map
 void Der2::bar() -> void Base::bar()
 void Der1::foo() -> void Base::foo()
Construction vtable for ('Der1', 0) in 'Join' (7 entries).
   0 | vbase_offset (0)
   1 | vcall_offset (0)
   2 | vcall_offset (0)
   3 | offset_to_top (0)
   4 | Der1 RTTI
       -- (Base, 0) vtable address --
       -- (Der1, 0) vtable address --
   5 | void Der1::foo()
   6 | void Base::bar() [pure]

Original map
 void Der2::bar() -> void Base::bar()
 void Der1::foo() -> void Base::foo()
Construction vtable for ('Der2', 8) in 'Join' (13 entries).
   0 | vbase_offset (-8)
   1 | vcall_offset (0)
   2 | vcall_offset (-8)
   3 | offset_to_top (0)
   4 | Der2 RTTI
       -- (Der2, 8) vtable address --
   5 | [unused] void Base::foo() [pure]
   6 | void Der2::bar()
   7 | vcall_offset (8)
   8 | vcall_offset (0)
   9 | offset_to_top (8)
  10 | Der2 RTTI
       -- (Base, 0) vtable address --
  11 | void Base::foo() [pure]
  12 | void Der2::bar()
       [this adjustment: 0 non-virtual, -32 vcall offset offset] method: void Base::bar()

Original map
 void Der2::bar() -> void Base::bar()
 void Der1::foo() -> void Base::foo()
Vtable for 'Der2' (7 entries).
   0 | vbase_offset (0)
   1 | vcall_offset (0)
   2 | vcall_offset (0)
   3 | offset_to_top (0)
   4 | Der2 RTTI
       -- (Base, 0) vtable address --
       -- (Der2, 0) vtable address --
   5 | void Base::foo() [pure]
   6 | void Der2::bar()

Virtual base offset offsets for 'Der2' (1 entry).
   Base | -40

Thunks for 'void Der2::bar()' (1 entry).
   0 | this adjustment: 0 non-virtual, -32 vcall offset offset

VTable indices for 'Der2' (1 entries).
   1 | void Der2::bar()

reference

  1. C++中虛函數、虛繼承內存模型 介紹了虛繼承的C++對象內存佈局
  2. C.129: When designing a class hierarchy, distinguish between implementation inheritance and interface inheritance
  3. C++ Decorate basic_iostream classes 教你如何繼承std::basic_streambuf和std::basic_ostream來實現自己的標準輸出。
  4. 【C++拾遺】 從內存佈局看C++虛繼承的實現原理
  5. Solving the Diamond Problem with Virtual Inheritance
  6. What is the “dreaded diamond”? 這是stdcpp上的FAQ,該問答之後的幾個問答都非常好。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章