To answer what happens/why when you run that code, I compiled it via
g++ -ggdb main.cc
, and stepped through with gdb.
main.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;
}
Setting a break point at main
, then stepping into B(), printing the this
ptr, taking a step into A() (base constructor):
(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
shows that this
initially points at the derived B obj b
being constructed on the stack at 0x7fffffffde80. The next step is into the base A() ctor and this
becomes A * const
to the same address, which makes sense as the base A is right in the start of B object. but it still hasn't been constructed:
(gdb) p *this
$30 = {_vptr.A = 0x7fffffffdf80, _n = 0}
One more step:
(gdb) s
4 fn();
(gdb) p *this
$31 = {_vptr.A = 0x402038 <vtable for A+16>, _n = 0}
_n has been initialized, and it's virtual function table pointer contains the address of virtual void A::fn()
:
(gdb) p fn
$32 = {void (A * const)} 0x40114a <A::fn()>
(gdb) x/1a 0x402038
0x402038 <_ZTV1A+16>: 0x40114a <_ZN1A2fnEv>
So it makes perfect sense that the next step executes A::fn() via this->fn() given the active this
and _vptr.A
. Another step and we're back in 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>}
The base A has been constructed. Note that address stored in the virtual function table pointer has changed to the vtable for derived class B. And so a call to fn() would select the derived class override B::fn() via this->fn() given the active this
and _vptr.A
(un-comment call to B::fn() in B() to see this.) Again examining 1 address stored in _vptr.A shows it now points to the derived class override:
(gdb) p fn
$36 = {void (B * const)} 0x401188 <B::fn()>
(gdb) x/1a 0x402020
0x402020 <_ZTV1B+16>: 0x401188 <_ZN1B2fnEv>
By looking at this example, and by looking at one with a 3 level inheritance, it appears that as the compiler descends to construct the base sub-objects, the type of this*
and the corresponding address in _vptr.A
change to reflect the current sub-object being constructed, - so it gets left pointing to the most derived type's. So we would expect virtual functions called from within ctors to choose the function for that level, i.e., same result as if they were non-virtual.. Likewise for dtors but in reverse. And this
becomes a ptr to member while members are being constructed so they also properly call any virtual functions that are defined for them.