Scott Meyers' More Effective C++, Item 24, "Understand the costs of virtual functions, multiple inheritance, virtual base classes, and RTTI", while not going into depth on this issue, explains how virtual base classes may increase object size by adding pointers for the virtual base class in both directly derived classes. It may not have anything do with virtual functions. The compiler may just find this the easiest approach to overcome the problem that b
and c
may have different sizes themselves and a
-member access may be through any of b
, c
or d
.
A lot of "may" here, and that's because the whole thing is completely compiler-specific, as the C++ standard does not specify memory layout for classes. It's strangely hard to find really good authorative documentation about memory layout for virtual base classes by compiler vendors on the Internet (which is perhaps because virtual inheritance is a very rarely used C++ language feature in the first place).
For GCC, you may find the -fdump-class-hierarchy
compiler option useful. Here's what it generates for your example on my machine (removing the standard-library stuff):
Class a
size=1 align=1
base size=0 base align=1
a (0x0x344a0a8) 0 empty
Vtable for b
b::_ZTV1b: 3u entries
0 0u
4 (int (*)(...))0
8 (int (*)(...))(& _ZTI1b)
VTT for b
b::_ZTT1b: 1u entries
0 ((& b::_ZTV1b) + 12u)
Class b
size=4 align=4
base size=4 base align=4
b (0x0x3460b40) 0 nearly-empty
vptridx=0u vptr=((& b::_ZTV1b) + 12u)
a (0x0x344a0e0) 0 empty virtual
vbaseoffset=-12
Vtable for c
c::_ZTV1c: 3u entries
0 0u
4 (int (*)(...))0
8 (int (*)(...))(& _ZTI1c)
VTT for c
c::_ZTT1c: 1u entries
0 ((& c::_ZTV1c) + 12u)
Class c
size=4 align=4
base size=4 base align=4
c (0x0x3460d40) 0 nearly-empty
vptridx=0u vptr=((& c::_ZTV1c) + 12u)
a (0x0x344a118) 0 empty virtual
vbaseoffset=-12
Vtable for d
d::_ZTV1d: 6u entries
0 0u
4 (int (*)(...))0
8 (int (*)(...))(& _ZTI1d)
12 4294967292u
16 (int (*)(...))-4
20 (int (*)(...))(& _ZTI1d)
Construction vtable for b (0x0x3460f00 instance) in d
d::_ZTC1d0_1b: 3u entries
0 0u
4 (int (*)(...))0
8 (int (*)(...))(& _ZTI1b)
Construction vtable for c (0x0x3460f40 instance) in d
d::_ZTC1d4_1c: 3u entries
0 4294967292u
4 (int (*)(...))0
8 (int (*)(...))(& _ZTI1c)
VTT for d
d::_ZTT1d: 4u entries
0 ((& d::_ZTV1d) + 12u)
4 ((& d::_ZTC1d0_1b) + 12u)
8 ((& d::_ZTC1d4_1c) + 12u)
12 ((& d::_ZTV1d) + 24u)
Class d
size=8 align=4
base size=8 base align=4
d (0x0x3460ec0) 0
vptridx=0u vptr=((& d::_ZTV1d) + 12u)
b (0x0x3460f00) 0 nearly-empty
primary-for d (0x0x3460ec0)
subvttidx=4u
a (0x0x344a150) 0 empty virtual
vbaseoffset=-12
c (0x0x3460f40) 4 nearly-empty
subvttidx=8u vptridx=12u vptr=((& d::_ZTV1d) + 24u)
a (0x0x344a150) alternative-path
For non-virtual inheritance, the memory layout would be much simpler:
Class a
size=1 align=1
base size=0 base align=1
a (0x0x344a0a8) 0 empty
Class b
size=1 align=1
base size=1 base align=1
b (0x0x346cb40) 0 empty
a (0x0x344a0e0) 0 empty
Class c
size=1 align=1
base size=1 base align=1
c (0x0x346cbc0) 0 empty
a (0x0x344a118) 0 empty
Class d
size=2 align=1
base size=2 base align=1
d (0x0x346cc40) 0 empty
b (0x0x346cc80) 0 empty
a (0x0x344a150) 0 empty
c (0x0x346ccc0) 1 empty
a (0x0x344a188) 1 empty
So GCC apparently implements virtual inheritance through vptrs and does not care about optimising it away when it's not needed.