Skip to content

多继承与虚继承:能用,但想清楚再用 ​

在前面的章节里,咱们讨论的都是单继承——一个类只有一个直接基类。这覆盖了绝大多数面向对象设计的需求。但 C++ 还允许一个类同时继承多个基类,这就是多继承(multiple inheritance)。但是这个东西争议极大:用好它能让设计更灵活,用不好则会让整个继承体系变得难以维护。(所以比起来笔者更信赖组合的方式。)笔者自己也是非常强烈的不推介随意的搞多继承。我所在的项目就因为多继承造成的崩溃问题熬夜查了很久!除非你真的知道自己为什么需要多继承,否则一概组合!

这一章咱们来搞清楚多继承的语法、菱形继承问题、虚继承的解决方案,以及什么时候该转身选择更安全的替代方案。

多继承的基本语法与使用场景 ​

咱们看多继承的语法本身并不复杂:一个类在继承列表中写上多个基类,用逗号分隔。派生类对象会包含所有基类的子对象,必须实现所有纯虚基类的接口。

展开代码收起代码共 34 行
C++
class Printable {
public:
    virtual ~Printable() = default;
    virtual void print() const = 0;
};

class Serializable {
public:
    virtual ~Serializable() = default;
    virtual std::string serialize() const = 0;
};

// 同时可打印、可序列化的配置项
class ConfigItem : public Printable, public Serializable {
private:
    std::string key_;
    std::string value_;

public:
    ConfigItem(std::string key, std::string value)
        : key_(std::move(key)), value_(std::move(value))
    {
    }

    void print() const override
    {
        printf("%s = %s\n", key_.c_str(), value_.c_str());
    }

    std::string serialize() const override
    {
        return "\"" + key_ + "\":\"" + value_ + "\"";
    }
};

咱们创建对象后,可以通过任意一个基类指针来操作它:Printable* p = &item; p->print(); 或 Serializable* s = &item; s->serialize();。构造顺序是基类按继承列表声明顺序构造,析构恰好相反。

当两个基类有同名成员时,编译器会报歧义错误,需要用 obj.BaseA::foo() 显式消歧。最安全的多继承用法是接口继承:所有基类都是纯虚接口,不含数据成员和具体实现。您要是发现自己试图通过多继承来复用代码实现而不是表达"具备多种能力"的语义,那大概率应该考虑组合。

菱形继承问题 ​

多继承中最经典的坑是菱形继承(diamond inheritance)——一个基类被两个中间类继承,最终类同时继承这两个中间类,形成菱形。不做特殊处理时,最终对象中会包含两份共同基类子对象。咱们来看具体例子:

C++
class Device {
public:
    int id;
    Device() : id(0) {}
};

class InputDevice : public Device {};
class OutputDevice : public Device {};

class TouchScreen : public InputDevice, public OutputDevice
{
};

TouchScreen ts;
// ts.id = 1;          // 编译错误:歧义!
ts.InputDevice::id = 1;
ts.OutputDevice::id = 2;  // 两份独立的 id,互不影响

咱们看到 Device 的构造函数被调用两次,id 是两份独立的拷贝。一个触摸屏设备应该只有一个 ID。更严重的是数据不一致——在大型系统中,这种"同一个逻辑对象内部存在两份不同步的状态"是极其难以追踪的 bug 来源。

虚继承解决菱形问题 ​

C++ 提供给咱们的解决方案是虚继承:在中间层类继承共同基类时加上 virtual 关键字:

C++
class InputDevice : virtual public Device {};
class OutputDevice : virtual public Device {};

class TouchScreen : public InputDevice, public OutputDevice
{
public:
    // 虚继承下,最底层的派生类负责初始化虚基类
    TouchScreen() : Device(), InputDevice(), OutputDevice() {}
};

咱们现在看到 Device 只构造一次,id 只有一份,不再有歧义。但虚继承从来是有代价的:对象布局引入了额外的虚基类指针(vbptr),sizeof(TouchScreen) 从 8 字节增长到约 24 字节,访问虚基类成员需要额外的间接寻址。

虚基类的构造由最底层派生类负责。中间层构造函数对虚基类的初始化列表会被静默忽略。咱们要是不知道这个规则,调试时可能会对着输出抓耳挠腮半天——"明明在中间类里传了参数,怎么没生效?"

虚继承必须出现在所有直接继承共同基类的中间类上。只让其中一个用 virtual 而另一个不用,菱形问题不会解决,编译器也不会报错,但咱们依然会得到两份基类子对象。

虚继承的对象布局与普通继承不同,reinterpret_cast 或 C 风格强转操作虚继承对象是极其危险的。static_cast 跨越虚基类边界时可能需要 this 指针偏移调整。咱们要是有把对象序列化成字节流的需求,虚继承会让事情变得非常棘手。

多继承的替代方案 ​

鉴于多继承尤其是虚继承的复杂性,在很多场景下咱们有更好的选择。

组合优于继承是面向对象设计中最经典的原则之一。咱们的类要是需要同时具备多种能力但不要求通过基类指针统一操作,直接持有成员对象往往比继承更清晰——把 Printer 和 JsonSerializer 作为成员变量持有,而不是作为基类继承。如果确实需要运行时多态,接口委托模式是比多继承更可控的选择:定义一个接口类,内部通过指针委托给具体实现。

总之,只要基类都是纯虚接口(没有数据成员、没有实现),多继承的复杂性就能被限制在可控范围内。您的多继承基类中要是出现了数据成员或具体方法实现,请立刻停下来重新审视您的设计。

动手验证——multi_inherit.cpp ​

咱们看一份涵盖多接口继承和菱形继承的完整可编译示例:

展开代码收起代码共 122 行
C++
// multi_inherit.cpp
// 编译:g++ -Wall -Wextra -std=c++17 -o multi_inherit multi_inherit.cpp

#include <cstdio>
#include <string>

// --- 接口多继承 ---
class Drawable {
public:
    virtual ~Drawable() = default;
    virtual void draw() const = 0;
};

class Serializable {
public:
    virtual ~Serializable() = default;
    virtual std::string serialize() const = 0;
};

class Shape : public Drawable, public Serializable {
protected:
    std::string name_;
public:
    explicit Shape(std::string name) : name_(std::move(name)) {}
};

class Circle : public Shape {
    double radius_;
public:
    explicit Circle(double r) : Shape("Circle"), radius_(r) {}
    void draw() const override {
        printf("[Draw] %s (r=%.2f)\n", name_.c_str(), radius_);
    }
    std::string serialize() const override {
        return "{\"type\":\"circle\",\"r\":" + std::to_string(radius_) + "}";
    }
};

class Rectangle : public Shape {
    double w_, h_;
public:
    Rectangle(double w, double h) : Shape("Rect"), w_(w), h_(h) {}
    void draw() const override {
        printf("[Draw] %s (%.2fx%.2f)\n", name_.c_str(), w_, h_);
    }
    std::string serialize() const override {
        return "{\"type\":\"rect\",\"w\":" + std::to_string(w_) +
               ",\"h\":" + std::to_string(h_) + "}";
    }
};

// --- 菱形继承(非虚) ---
class Component {
public:
    int version;
    Component() : version(1) { printf("  Component()\n"); }
};

class Renderer : public Component {
public:
    Renderer() { printf("  Renderer()\n"); }
    void render() { printf("  Render (v=%d)\n", version); }
};

class EventHandler : public Component {
public:
    EventHandler() { printf("  EventHandler()\n"); }
    void click() { printf("  Click (v=%d)\n", version); }
};

class Widget : public Renderer, public EventHandler {
public:
    Widget() { printf("  Widget()\n"); }
};

// --- 菱形继承(虚继承) ---
class VComponent {
public:
    int version;
    VComponent() : version(1) { printf("  VComponent()\n"); }
};

class VRenderer : virtual public VComponent {
public:
    VRenderer() { printf("  VRenderer()\n"); }
    void render() { printf("  Render (v=%d)\n", version); }
};

class VEventHandler : virtual public VComponent {
public:
    VEventHandler() { printf("  VEventHandler()\n"); }
    void click() { printf("  Click (v=%d)\n", version); }
};

class VWidget : public VRenderer, public VEventHandler {
public:
    VWidget() : VComponent(), VRenderer(), VEventHandler() {
        printf("  VWidget()\n");
    }
};

int main()
{
    printf("=== Interface Multi-Inheritance ===\n");
    Circle c(5.0);
    Rectangle r(3.0, 4.0);
    Drawable* drawables[] = {&c, &r};
    for (auto* d : drawables) { d->draw(); }

    printf("\n=== Diamond (no virtual) ===\n");
    Widget w;
    w.Renderer::version = 2;
    w.EventHandler::version = 3;
    printf("  sizeof(Widget) = %zu\n", sizeof(Widget));

    printf("\n=== Diamond (virtual) ===\n");
    VWidget vw;
    vw.version = 42;  // OK!只有一份
    printf("  sizeof(VWidget) = %zu\n", sizeof(VWidget));

    return 0;
}

编译并运行:

text
$ g++ -Wall -Wextra -std=c++17 -o multi_inherit multi_inherit.cpp
$ ./multi_inherit
=== Interface Multi-Inheritance ===
[Draw] Circle (r=5.00)
[Draw] Rect (3.00x4.00)

=== Diamond (no virtual) ===
  Component()
  Renderer()
  Component()
  EventHandler()
  Widget()
  sizeof(Widget) = 8

=== Diamond (virtual) ===
  VComponent()
  VRenderer()
  VEventHandler()
  VWidget()
  sizeof(VWidget) = 24

咱们对比两组输出:非虚继承中 Component 被构造两次,两份 version 独立变化;虚继承中 VComponent 只构造一次,version 统一。同时注意 sizeof 的差异——虚继承引入了额外指针开销。

练习 ​

练习 1:多接口实现 ​

请您设计一个 LogEntry 类,同时实现三个纯虚接口:IPrintable(void print() const)、ISerializable(std::string to_string() const 返回 JSON)、IFilterable(bool matches(const std::string& keyword) const)。LogEntry 包含 timestamp(整型)、level(如 "INFO")和 message(字符串)三个字段。创建若干条日志,分别通过三种基类指针来操作它们。

练习 2:修复菱形继承 ​

下面这段代码存在菱形继承问题。请您使用虚继承修复它,确保 SmartDevice 只包含一份 Device 子对象:

展开代码收起代码共 25 行
C++
class Device {
public:
    int device_id;
    Device(int id) : device_id(id) {}
};

class Networkable : public Device {
public:
    Networkable(int id) : Device(id) {}
    virtual void connect() = 0;
};

class Monitorable : public Device {
public:
    Monitorable(int id) : Device(id) {}
    virtual int read_status() = 0;
};

class SmartDevice : public Networkable, public Monitorable
{
public:
    SmartDevice(int id) : Networkable(id), Monitorable(id) {}
    void connect() override { printf("Connected\n"); }
    int read_status() override { return device_id; }  // 歧义!
};

提示:咱们修改后别忘了在 SmartDevice 的构造函数初始化列表中直接初始化虚基类 Device。

pdf-latest-70-g3de3d0a · 3de3d0a · 2026-09-27