为了回答运行该代码时会发生什么/为什么,我通过 编译它
g++ -ggdb main.cc
,并使用 gdb 逐步完成。
主.cc:
class A {
public:
A() {
fn();
}
virtual void fn() { _n=1; }
int getn() { return _n; }
protected:
int _n;
};
class B: public A {
public:
B() {
// fn();
}
void fn() override {
_n = 2;
}
};
int main() {
B b;
}
在 处设置断点main
,然后进入 B(),打印this
ptr,进入 A()(基本构造函数):
(gdb) step
B::B (this=0x7fffffffde80) at main2.cc:16
16 B() {
(gdb) p this
$27 = (B * const) 0x7fffffffde80
(gdb) p *this
$28 = {<A> = {_vptr.A = 0x7fffffffdf80, _n = 0}, <No data fields>}
(gdb) s
A::A (this=0x7fffffffde80) at main2.cc:3
3 A() {
(gdb) p this
$29 = (A * const) 0x7fffffffde80
显示this
最初指向b
在 0x7fffffffde80 的堆栈上构造的派生 B obj。下一步是进入基 A() ctor 并this
变为A * const
相同的地址,这是有道理的,因为基 A 正好位于 B 对象的开头。但它仍然没有被构建:
(gdb) p *this
$30 = {_vptr.A = 0x7fffffffdf80, _n = 0}
再一步:
(gdb) s
4 fn();
(gdb) p *this
$31 = {_vptr.A = 0x402038 <vtable for A+16>, _n = 0}
_n 已初始化,它的虚函数表指针包含以下地址virtual void A::fn()
:
(gdb) p fn
$32 = {void (A * const)} 0x40114a <A::fn()>
(gdb) x/1a 0x402038
0x402038 <_ZTV1A+16>: 0x40114a <_ZN1A2fnEv>
因此,下一步通过 this->fn() 给定活动的this
和_vptr.A
. 再走一步,我们又回到了 B() ctor:
(gdb) s
B::B (this=0x7fffffffde80) at main2.cc:18
18 }
(gdb) p this
$34 = (B * const) 0x7fffffffde80
(gdb) p *this
$35 = {<A> = {_vptr.A = 0x402020 <vtable for B+16>, _n = 1}, <No data fields>}
基地A已经建成。请注意,存储在虚函数表指针中的地址已更改为派生类 B 的 vtable。因此,对 fn() 的调用将通过 this->fn() 选择派生类覆盖 B::fn()this
并且_vptr.A
(取消注释 B() 中对 B::fn() 的调用以查看此内容。)再次检查存储在 _vptr.A 中的 1 个地址,显示它现在指向派生类覆盖:
(gdb) p fn
$36 = {void (B * const)} 0x401188 <B::fn()>
(gdb) x/1a 0x402020
0x402020 <_ZTV1B+16>: 0x401188 <_ZN1B2fnEv>
通过查看此示例,并查看具有 3 级继承的示例,似乎随着编译器下降以构造基本子对象,其类型this*
和相应地址会_vptr.A
发生变化以反映当前正在构造的子对象, - 所以它指向最派生的类型。因此,我们希望从 ctors 中调用的虚函数为该级别选择函数,即,结果与非虚函数相同。对于 dtors 也是如此,但相反。并且this
在构造成员时成为成员的 ptr,因此它们也可以正确调用为它们定义的任何虚函数。