C++菱形继承初始化问题:默认构造函数被忽略?
可能重复的问题
- Diamond Problem C++: Derived class of diamond calls default constructor
问题示例代码
#include <iostream> #include <cassert> struct MyEnum { enum valid { CASE1, CASE2, DEFAULT }; valid value; MyEnum(valid value = DEFAULT) : value(value) {} }; class Base { protected: MyEnum value; public: Base() = default; Base(MyEnum value) : value(value) { std::cout << "Base::Base(MyEnum).\n"; } }; class ReadableBase : public virtual Base { public: ReadableBase() = default; ReadableBase(MyEnum value) : Base(value) { std::cout << "ReadableBase::ReadableBase(MyEnum).\n"; } public: MyEnum read() { return value; } }; class WriteableBase : public virtual Base { public: WriteableBase() = default; WriteableBase(MyEnum value) : Base(value) { std::cout << "WriteableBase::WriteableBase(MyEnum).\n"; } public: void write(MyEnum value) { this->value = value; } }; class ReadWriteBase : public ReadableBase, public WriteableBase { public: ReadWriteBase() = default; ReadWriteBase(MyEnum value) : ReadableBase(value), WriteableBase(value) {} }; int main(int, char*[]) { // 错误初始化为value = MyEnum::valid::DEFAULT ReadWriteBase rw(MyEnum::valid::CASE1); // 此时操作正常 rw.write(MyEnum::valid::CASE2); }
问题现象
尽管ReadWriteBase传入了MyEnum::valid::CASE1,但程序中Base::value以及其派生类中的对应值都是MyEnum::valid::DEFAULT,且Base::Base(MyEnum)构造函数并未被调用:
assert(rw.ReadableBase::value == MyEnum::valid::DEFAULT); // true assert(rw.WriteableBase::value == MyEnum::valid::DEFAULT); // true assert(rw.Base::value == MyEnum::valid::DEFAULT); // true
核心问题
- 该现象的原因是什么?如何正确实现初始化?
- 是否有更优的实现方案?
原因分析
在C++的虚拟继承机制中,虚拟基类的初始化由最底层的派生类(即ReadWriteBase)负责,而非中间的派生类(ReadableBase、WriteableBase)。
当创建ReadWriteBase对象时,即使ReadableBase和WriteableBase的构造函数中写了Base(value),这些调用会被编译器忽略——因为虚拟基类的初始化权限被移交给了最底层的派生类。此时ReadWriteBase的构造函数没有显式调用Base的带参构造,所以编译器会自动调用Base的默认构造函数,导致value被初始化为DEFAULT。
正确的初始化实现
修改ReadWriteBase的构造函数,显式调用虚拟基类Base的带参构造:
class ReadWriteBase : public ReadableBase, public WriteableBase { public: ReadWriteBase() = default; // 显式初始化虚拟基类Base ReadWriteBase(MyEnum value) : Base(value), ReadableBase(value), WriteableBase(value) {} };
这样创建ReadWriteBase对象时,会先调用Base::Base(MyEnum)完成初始化,再依次调用ReadableBase和WriteableBase的构造函数,最终value会被正确设置为传入的CASE1。
更优的实现方案
如果场景允许,可以考虑以下几种更简洁的设计:
1. 组合替代继承
避免菱形继承的复杂性,采用组合的方式封装读写功能:
class ReadWrite { private: MyEnum value; public: ReadWrite(MyEnum val = MyEnum::DEFAULT) : value(val) {} MyEnum read() const { return value; } void write(MyEnum val) { value = val; } };
这种方式没有继承层级的问题,代码更直观,也符合"优先使用组合而非继承"的设计原则。
2. 抽象接口+实现类
定义纯虚的读写接口,再由具体类实现:
class Readable { public: virtual MyEnum read() const = 0; virtual ~Readable() = default; }; class Writeable { public: virtual void write(MyEnum val) = 0; virtual ~Writeable() = default; }; class ReadWriteImpl : public Readable, public Writeable { private: MyEnum value; public: ReadWriteImpl(MyEnum val = MyEnum::DEFAULT) : value(val) {} MyEnum read() const override { return value; } void write(MyEnum val) override { value = val; } };
这种方式通过接口分离关注点,同时避免了虚拟继承的初始化问题,扩展性更好。
内容的提问来源于stack exchange,提问作者buzzysin

