本篇源碼部分來自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
- C++中虛函數、虛繼承內存模型 介紹了虛繼承的C++對象內存佈局
- C.129: When designing a class hierarchy, distinguish between implementation inheritance and interface inheritance
- C++ Decorate basic_iostream classes 教你如何繼承std::basic_streambuf和std::basic_ostream來實現自己的標準輸出。
- 【C++拾遺】 從內存佈局看C++虛繼承的實現原理
- Solving the Diamond Problem with Virtual Inheritance
- What is the “dreaded diamond”? 這是stdcpp上的FAQ,該問答之後的幾個問答都非常好。