C++ 继承与多态详解
1. 前言
继承(Inheritance)和多态(Polymorphism)是 C++ 面向对象编程(OOP)的两大支柱。
- 继承:允许一个类(派生类/子类)从另一个类(基类/父类)获取属性和方法,实现代码复用和层次结构。
- 多态:允许同一接口在不同对象上表现出不同行为,实现'一个接口、多种方法'。
继承提供'是什么'(is-a 关系),多态提供'怎么做'的灵活性。在现代 C++(C++17/20)中,继承与多态常用于 GUI(如 Qt QWidget 继承链)、游戏引擎(如 Unreal Actor)、插件系统(如动态加载 DLL)。
2. 继承的概念与分类
2.1 继承的基本定义
继承语法:
class Derived : public Base {
// public 继承(最常用)
// 派生类成员
};
示例(简单继承):
#include <iostream>
using namespace std;
class Animal {
public:
void eat() { cout << "动物在吃...\n"; }
};
class Dog : public Animal {
public:
void bark() { cout << "汪汪汪!\n"; }
};
int main() {
Dog d;
d.eat(); // 继承自基类
d.bark(); // 派生类自身
return 0;
}
输出:
动物在吃... 汪汪汪!
2.2 继承的访问控制
| 基类成员 | public 继承 | protected 继承 | private 继承 |
|---|---|---|---|
| public | public | protected | private |
| protected | protected | protected | private |
| private | 不可访问 | 不可访问 | 不可访问 |
推荐:99% 场景用 public 继承(保持基类 public 接口公开)。
2.3 多重继承与菱形继承问题
多重继承:一个类继承多个基类。
问题:菱形继承导致基类成员二义性。
class A {
public:
int x = 1;
};
class B : public A {};
class C : public A {};
class D : public B, public C {};
int main() {
D d;
// cout << d.x; // 错误:二义性(两个 x)
return 0;
}
2.4 虚继承(virtual inheritance)
解决菱形问题:用 virtual 继承,确保基类只存在一份。
class B : virtual public A {};
class C : virtual public A {};
class D : public B, public C {};
int main() {
D d;
cout << d.x; // OK,只有一个 x
return 0;
}
3. 多态的概念与实现
3.1 多态的分类
- 静态多态(编译期):函数重载、模板
- 动态多态(运行期):虚函数 + 继承 + 基类指针/引用
3.2 虚函数与 vtable 机制
示例(动态多态):
class Animal {
public:
virtual void speak() const {
cout << "动物在叫...\n";
} // 虚函数
virtual ~Animal() = default; // 虚析构(必须!)
};
class Dog : public Animal {
public:
void speak() const override {
cout << "汪汪汪!\n";
}
};
int main() {
Animal* a = new Dog(); // 基类指针指向派生类
a->speak(); // 动态多态:调用 Dog::speak()
delete a; // 虚析构:先 Dog 析构,再 Animal
return 0;
}
vtable 原理(底层):
- 每个有虚函数的类有一个 vtable(虚函数表),存放函数指针
- 对象头有一个 vptr(虚指针),指向类 vtable
- 调用:
a->vptr -> vtable[speak_offset] -> Dog::speak()
3.3 override / final 关键字
class Cat : public Animal {
public:
void speak() const override final {
// override 强制检查,final 禁止子类重写
cout << "喵喵喵!\n";
}
};
4. 继承与多态的结合实践
完整 Demo(VS 项目:PolymorphismInheritanceDemo)
animal.h
#pragma once
#include <string>
#include <iostream>
class Animal {
public:
virtual ~Animal() = default;
virtual void speak() const = 0;
};
dog.h
#pragma once
#include "animal.h"
class Dog : public Animal {
public:
void speak() const override {
std::cout << "汪汪汪!我是狗狗。\n";
}
};
main.cpp
#include "dog.h"
#include <memory>
#include <vector>
int main() {
std::vector<std::unique_ptr<Animal>> zoo;
zoo.push_back(std::make_unique<Dog>());
// zoo.push_back(std::make_unique<Cat>()); // 假设有 Cat 类
for (const auto& animal : zoo) {
animal->speak(); // 继承 + 多态
}
return 0;
}
5. 常见陷阱与最佳实践
- 陷阱:基类无虚析构 → delete 基类指针时派生析构不调用
- 陷阱:多重继承无 virtual → 数据重复
- 最佳实践:基类虚函数 + 虚析构
- 最佳实践:用 override 检查重写
- 最佳实践:优先 unique_ptr 管理多态对象
6. 总结
继承实现代码复用,多态实现行为灵活。两者结合是 C++ OOP 的精髓,在 Qt、Unreal 等框架中广泛应用。
7. Qt 中继承与多态的基本原则
Qt 框架大量使用继承与多态,以下是必须记住的几条铁律:
- 所有
QObject派生类都隐含虚析构(即使不写virtual ~QObject(),moc 也会生成) - 只要类中定义了信号或槽,必须写
Q_OBJECT宏(否则 moc 不工作) - 自定义控件通常继承
QWidget、QAbstractItemView、QAbstractItemModel等 - 多态指针/智能指针管理对象时,必须用
virtual析构函数 - Qt 不鼓励在信号槽中抛出异常(跨线程行为未定义)
8. 最常见的继承场景:自定义控件(QWidget 继承链)
// MyButton.h
#pragma once
#include <QPushButton>
class MyButton : public QPushButton {
Q_OBJECT
public:
explicit MyButton(const QString &text, QWidget *parent = nullptr);
protected:
void paintEvent(QPaintEvent *event) override; // 重写绘制
void mousePressEvent(QMouseEvent *event) override;
};
// MyButton.cpp
#include "MyButton.h"
#include <QPainter>
#include <QMouseEvent>
MyButton::MyButton(const QString &text, QWidget *parent) : QPushButton(text, parent) {
setFixedSize(120, 40);
}
void MyButton::paintEvent(QPaintEvent *event) {
QPainter painter(this);
painter.setRenderHint(QPainter::Antialiasing); // 自定义圆角背景
painter.setBrush(QColor("#4CAF50"));
painter.setPen(Qt::NoPen);
painter.drawRoundedRect(rect(), 20, 20); // 调用基类绘制文字
QPushButton::paintEvent(event);
}
void MyButton::mousePressEvent(QMouseEvent *event) {
// 按下时变色
setStyleSheet("background-color: #45a049;");
QPushButton::mousePressEvent(event);
}
在主窗口使用(多态):
MyButton *btn = new MyButton("自定义按钮", this);
btn->move(50, 50);
9. 信号槽中的多态(运行时动态绑定)
Qt 信号槽本质上是运行时多态的一种实现形式。
class Sensor : public QObject {
Q_OBJECT
public:
explicit Sensor(QObject *parent = nullptr) : QObject(parent) {}
signals:
void valueChanged(double value);
};
class TemperatureSensor : public Sensor {
Q_OBJECT
public:
void simulate() {
double temp = 25.0 + (rand() % 10);
emit valueChanged(temp);
}
};
class PressureSensor : public Sensor {
Q_OBJECT
public:
void simulate() {
double press = 101.3 + (rand() % 5);
emit valueChanged(press);
}
};
在主窗口连接(多态):
Sensor *sensor = new TemperatureSensor(this); // 基类指针
connect(sensor, &Sensor::valueChanged, this, [](double v) {
qDebug() << "传感器数值:" << v;
}); // 即使换成 PressureSensor,也能正常工作
sensor = new PressureSensor(this);
connect(sensor, &Sensor::valueChanged, this, [](double v) {
qDebug() << "传感器数值:" << v;
});
10. 模型 - 视图架构中的多态(QAbstractItemModel)
这是 Qt 中最经典的多态实践之一。
自定义简单表格模型
// SimpleTableModel.h
#pragma once
#include <QAbstractTableModel>
class SimpleTableModel : public QAbstractTableModel {
Q_OBJECT
public:
explicit SimpleTableModel(QObject *parent = nullptr);
int rowCount(const QModelIndex &parent = {}) const override;
int columnCount(const QModelIndex &parent = {}) const override;
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
QVariant headerData(int section, Qt::Orientation orientation, int role) const override;
private:
QVector<QStringList> m_data;
};
在主窗口使用(多态绑定)
QTableView *view = new QTableView(this);
view->setModel(new SimpleTableModel(this)); // 基类指针指向派生类
11. 插件系统中的多态(QPluginLoader + 接口)
// plugininterface.h
#pragma once
#include <QtPlugin>
class PluginInterface {
public:
virtual ~PluginInterface() = default;
virtual QString name() const = 0;
virtual void execute() = 0;
};
#define PluginInterface_iid "org.example.PluginInterface/1.0"
Q_DECLARE_INTERFACE(PluginInterface, PluginInterface_iid)
具体插件(编译成 .dll / .so)
// myplugin.cpp
#include "plugininterface.h"
#include <QDebug>
class MyPlugin : public QObject, public PluginInterface {
Q_OBJECT
Q_PLUGIN_METADATA(IID PluginInterface_iid)
Q_INTERFACES(PluginInterface)
public:
QString name() const override {
return "示例插件";
}
void execute() override {
qDebug() << "插件执行中...";
}
};
加载插件(多态)
QPluginLoader loader("plugins/myplugin.dll");
if (QObject *plugin = loader.instance()) {
if (auto* myPlugin = qobject_cast<PluginInterface*>(plugin)) {
qDebug() << "加载成功:" << myPlugin->name();
myPlugin->execute();
}
}
12. 完整示例项目:多态形状编辑器
项目名称:ShapeEditor
shape.h
#pragma once
#include <QObject>
#include <QPainter>
class Shape : public QObject {
Q_OBJECT
public:
explicit Shape(QObject *parent = nullptr) : QObject(parent) {}
virtual ~Shape() = default;
virtual void draw(QPainter *painter) const = 0;
virtual QString type() const = 0;
};
circle.h
#pragma once
#include "shape.h"
class Circle : public Shape {
Q_OBJECT
public:
Circle(qreal x, qreal y, qreal r, QObject *parent = nullptr) : Shape(parent), m_x(x), m_y(y), m_r(r) {}
void draw(QPainter *painter) const override {
painter->drawEllipse(m_x - m_r, m_y - m_r, m_r * 2, m_r * 2);
}
QString type() const override {
return "圆形";
}
private:
qreal m_x, m_y, m_r;
};
mainwindow.cpp(核心多态部分)
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include "circle.h"
#include <memory>
#include <vector>
class Canvas : public QWidget {
public:
std::vector<std::unique_ptr<Shape>> shapes;
protected:
void paintEvent(QPaintEvent *) override {
QPainter painter(this);
painter.setRenderHint(QPainter::Antialiasing);
for (const auto& shape : shapes) {
shape->draw(&painter); // 多态调用
}
}
};
MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWindow) {
ui->setupUi(this);
setWindowTitle("多态形状编辑器");
Canvas *canvas = new Canvas(this);
setCentralWidget(canvas);
QPushButton *btn = new QPushButton("添加圆", this);
connect(btn, &QPushButton::clicked, this, [=]() {
canvas->shapes.push_back(std::make_unique<Circle>(100, 100, 50));
canvas->update(); // 触发重绘
});
// ... 其他按钮、布局省略
}
13. 常见陷阱与最佳实践
| 陷阱 | 后果 | 最佳实践 |
|---|---|---|
| 基类无虚析构 | delete 基类指针时派生析构不调用 | 基类始终 virtual ~Base() = default; |
| 信号槽连接写错类名 | 运行时无反应 | 使用新式连接语法:&Class::signal |
| QAbstractItemModel 未正确实现 | 视图显示空白 | 至少实现 index、parent、rowCount、columnCount、data |
| 多重继承未用虚继承 | 数据成员重复 | 菱形继承时用 virtual 继承 |
| 插件接口未声明 Q_DECLARE_INTERFACE | qobject_cast 失败 | 必须定义 IID 并使用 Q_DECLARE_INTERFACE |
14. 总结
Qt 中的继承与多态是框架的基石:
- 继承:QWidget、QObject、QAbstractItemModel 等继承链
- 多态:信号槽、模型视图、插件系统、自定义控件
- 推荐模式:策略模式(支付方式)、工厂模式(形状创建)、观察者模式(信号槽本身)
15. Qt 中设计模式实践
内容覆盖 Qt 开发中最常用、最有代表性的几种设计模式。
15.1 观察者模式(Observer)—— Qt 信号槽的本质
Qt 中的观察者模式 = 信号槽机制 Qt 把经典观察者模式做到了极致:松耦合、跨线程、动态连接/断开、一对多、多对多。
典型实践:自定义信号 + 槽函数
// sensor.h
#pragma once
#include <QObject>
class Sensor : public QObject {
Q_OBJECT
public:
explicit Sensor(QObject *parent = nullptr) : QObject(parent) {}
void simulateData() {
double value = 20.0 + (rand() % 20);
emit dataReady(value);
}
signals:
void dataReady(double value); // 被观察者发出信号
};
// mainwindow.cpp(观察者)
connect(sensor, &Sensor::dataReady, this, [](double v) {
ui->valueLabel->setText(QString("当前值:%1").arg(v));
});
高级用法:动态连接多个观察者
QObject::connect(sensor, &Sensor::dataReady, ui->gaugeWidget, &GaugeWidget::updateValue);
QObject::connect(sensor, &Sensor::dataReady, logger, &Logger::logValue);
Qt 特色:
- 跨线程安全(QueuedConnection)
- 动态断开(disconnect)
- Lambda 槽(C++11+)
15.2 策略模式(Strategy)—— 动态切换算法
适用场景:支付方式、排序算法、绘图渲染策略等。
示例:多种支付方式
// paymentstrategy.h
#pragma once
#include <QObject>
class PaymentStrategy : public QObject {
Q_OBJECT
public:
virtual ~PaymentStrategy() = default;
virtual bool pay(double amount) = 0;
virtual QString name() const = 0;
};
// wechatpay.h
#pragma once
#include "paymentstrategy.h"
class WeChatPay : public PaymentStrategy {
Q_OBJECT
public:
bool pay(double amount) override {
qDebug() << "微信支付成功:" << amount;
return true;
}
QString name() const override {
return "微信支付";
}
};
上下文类(使用策略)
class PaymentContext : public QObject {
Q_OBJECT
private:
std::unique_ptr<PaymentStrategy> strategy_;
public:
void setStrategy(std::unique_ptr<PaymentStrategy> s) {
strategy_ = std::move(s);
}
bool execute(double amount) {
if (strategy_) {
return strategy_->pay(amount);
}
return false;
}
};
使用:
PaymentContext ctx;
ctx.setStrategy(std::make_unique<WeChatPay>());
ctx.execute(100.0); // 输出:微信支付成功:100
15.3 工厂模式(Factory)—— 对象创建抽象
典型应用:动态创建形状、插件、控件等。
shape.h
#pragma once
#include <QObject>
class Shape : public QObject {
Q_OBJECT
public:
virtual void draw(QPainter *p) const = 0;
virtual QString type() const = 0;
};
factory.h
#pragma once
#include <memory>
#include "shape.h"
class ShapeFactory {
public:
static std::unique_ptr<Shape> create(const QString &type);
};
shape_factory.cpp
#include "factory.h"
#include "circle.h"
#include "rectangle.h"
std::unique_ptr<Shape> ShapeFactory::create(const QString &type) {
if (type == "circle") return std::make_unique<Circle>();
if (type == "rectangle") return std::make_unique<Rectangle>();
return nullptr;
}
使用:
auto shape = ShapeFactory::create("circle");
if (shape) shape->draw(&painter);
15.4 适配器模式(Adapter)—— 接口转换
场景:老系统接口与 Qt 不兼容,或第三方库需要适配。
oldlogger.h(模拟旧接口)
class OldLogger {
public:
void log(const char* msg) {
printf("[OLD] %s\n", msg);
}
};
qtloggeradapter.h
#pragma once
#include <QObject>
#include "oldlogger.h"
class QtLoggerAdapter : public QObject {
Q_OBJECT
private:
OldLogger old_;
public:
explicit QtLoggerAdapter(QObject *parent = nullptr) : QObject(parent) {}
void info(const QString &msg) {
old_.log(msg.toUtf8().constData());
emit messageLogged(msg);
}
signals:
void messageLogged(const QString &msg);
};
使用:
QtLoggerAdapter adapter;
connect(&adapter, &QtLoggerAdapter::messageLogged, this, [](const QString &m) {
ui->logBrowser->append(m);
});
adapter.info("用户登录成功");
15.5 完整示例项目:多态形状编辑器(综合多种模式)
项目名称:ShapeEditorQt
核心类结构:
- Shape(抽象基类) ← Circle / Rectangle(具体形状)
- ShapeFactory(工厂模式)
- DrawCommand(命令模式,支持撤销)
- CanvasWidget(继承 QWidget,重写 paintEvent)
shape.h(抽象接口)
#pragma once
#include <QObject>
#include <QPainter>
class Shape : public QObject {
Q_OBJECT
public:
explicit Shape(QObject *parent = nullptr) : QObject(parent) {}
virtual ~Shape() = default;
virtual void draw(QPainter *p) const = 0;
virtual QString type() const = 0;
virtual Shape* clone() const = 0; // 支持复制
};
circle.h
#pragma once
#include "shape.h"
class Circle : public Shape {
Q_OBJECT
public:
Circle(qreal x, qreal y, qreal r, QObject *parent = nullptr);
void draw(QPainter *p) const override;
QString type() const override { return "圆形"; }
Shape* clone() const override { return new Circle(*this); }
private:
qreal m_x, m_y, m_r;
};
mainwindow.cpp(综合使用)
// 添加形状(工厂 + 多态)
void MainWindow::on_addCircle_clicked() {
auto circle = std::make_unique<Circle>(100, 100, 50);
canvas->addShape(std::move(circle));
canvas->update();
}
// 撤销(命令模式)
void MainWindow::on_undo_clicked() {
canvas->undo();
}
16. 常见问题与最佳实践总结
| 陷阱 | 后果 | 最佳实践 |
|---|---|---|
| 基类无虚析构 | delete 基类指针内存泄漏 | 所有接口类写 virtual ~Base() = default; |
| Q_OBJECT 宏遗漏 | 信号槽失效 | 凡是需要信号/槽的类必须加 Q_OBJECT |
| 多重继承未用虚继承 | 数据重复、二义性 | 菱形继承时用 virtual public Base |
| 插件接口未定义 IID | qobject_cast 失败 | 必须写 Q_DECLARE_INTERFACE + IID 宏 |
| 在信号槽中抛异常 | 跨线程未定义行为 | 槽函数内捕获异常,转为 error 信号 |
17. 总结与进阶方向
Qt 中的继承与多态几乎无处不在:
- 继承:控件体系(QWidget → QPushButton → MyButton)
- 多态:信号槽、模型视图、插件系统、自定义绘制
进阶方向:
- QML + C++ 多态(QAbstractListModel + ListView)
- Qt State Machine(状态机 + 多态行为)
- Qt Plugin 动态加载多态组件
- 结合 C++20 concept 约束接口

