89 Advanced The Hundred
The Base Class You Have Twice
two writes to one int, and both survive
struct Device {
int id = 0;
void assign(int n) { id = n; }
};
struct Reader : Device {};
struct Writer : Device {};
struct Duplex : Reader, Writer {};
int main() {
Duplex d;
d.Reader::assign(7);
d.Writer::assign(9); // same id, second time... right?
std::cout << "Reader / Writer id: " << d.Reader::id << " / " << d.Writer::id << "\n";
std::cout << "Device / Duplex size: " << sizeof(Device) << " / " << sizeof(Duplex) << "\n";
}
Run it. Did the second assign overwrite the first?
Answer
No — the ids print as 7 / 9, the sizes as 4 / 8. There is no the id; there are two.
Why¶
With ordinary inheritance every base subobject is complete and independent, so Duplex —
reaching Device through both Reader and Writer — contains two whole Device
subobjects side by side: 8 bytes for a class whose only data member is one int. The two
assign calls write to different memory, so the id you get is the one you name — and name
one you must: unqualified d.id doesn't compile, GCC listing the same candidate twice, deadpan.
error: request for member 'id' is ambiguous
note: candidates are: 'int Device::id'
note: 'int Device::id'
Device* p = &d; is rejected the same way, and -Wall -Wextra never mentions the duplication.
The fix¶
Ask for a shared base with virtual, at both midpoints of the diamond:
Duplex now holds one shared Device: the ids print 9 / 9, d.id compiles, Device* p =
&d; works. The price is an indirection to reach that base (size 24 here) and a rule that
catches everyone — the most derived class initializes the virtual base. Give Device only
a Device(int) and the Device(1)/Device(2) in Reader and Writer are ignored: Duplex
itself must say Duplex() : Device(99), or the build fails with use of deleted function.
Takeaway: plain inheritance duplicates the base, virtual inheritance shares it — and only the most derived class may initialize a virtual base.
Try it: g++ -std=c++17 -Wall -Wextra main.cpp -o demo && ./demo; -DSHOW_BUG adds d.id.