【Qt】Qt 类的继承与内存管理详解:QObject、信号槽与隐式共享
【Qt】Qt 类的继承与内存管理详解:QObject、信号槽与隐式共享
在刚开始开发中,想当然的以为Qt的类都是继承自QObject其实不是。导致有时候写的一个继承自比如QGraphicsRectItem的自定义类,不能使用信号槽,但是发现加了Q_OBJECT宏 但是还是报错;最后发现应该这样写就可以了。
class GameItem : public QObject, public QGraphicsRectItem {//必须把 QObject 放在第一位继承,否则会导致 moc 解析失败!
Q_OBJECT
public:
};
在 Qt 中,并不是所有的类都继承自 QObject
。通常,Qt 中的类可以分为两大类:
- 继承
QObject
的类 → 这些类支持信号槽、对象树管理、属性系统等。 - 不继承
QObject
的类 → 这些类通常是轻量级的数据类、图形类,或者需要频繁拷贝的类。
1. 继承 QObject
的类(支持信号槽)
这些类通常是 管理对象、UI 组件、后台服务 等,主要用于事件驱动编程。
🔹 典型的继承 QObject
的类
类名 | 作用 |
---|---|
QObject | 所有 QObject -based 类的基类 |
QApplication / QGuiApplication | 应用程序管理 |
QWidget | 所有 UI 界面组件的基类 |
QMainWindow / QDialog / QLabel | 窗口组件 |
QPushButton / QLineEdit / QTextEdit | 常见 UI 控件 |
QThread | 线程管理 |
QTimer | 定时器 |
QNetworkAccessManager | 网络请求 |
QAbstractItemModel / QStandardItemModel | MVC 数据模型 |
QGraphicsObject | 继承 QObject 的 QGraphicsItem (可以用信号槽) |
QWidget
是QObject
的子类,所以所有 UI 控件(QPushButton
、QLabel
等)都能用信号槽!
2. 不继承 QObject
的类(不支持信号槽)
这些类主要是 数据结构、数学运算、图形元素,它们需要 支持拷贝、性能优化,因此 不能继承 QObject
(QObject
禁止拷贝)。
🔹 典型的不继承 QObject
的类
类别 | 代表类 | 说明 |
---|---|---|
数据结构 | QString / QByteArray / QVariant / QList / QMap | 这些类是 Qt 提供的基本数据类型 |
几何类 | QPoint / QRect / QSize / QColor | 表示点、矩形、大小、颜色 |
绘图类 | QImage / QPixmap / QPainter | 用于绘图、图像处理 |
模型-视图框架 | QModelIndex / QItemSelection | MVC 相关类 |
多线程 | QMutex / QWaitCondition | 线程同步工具 |
图形视图框架 | QGraphicsItem / QGraphicsRectItem / QGraphicsPixmapItem | 这些类用于 QGraphicsScene ,但不继承 QObject |
是否继承 QObject | 类的分类 | 示例 |
---|---|---|
✅ 继承 QObject | 事件管理 | QApplication / QWidget / QThread / QTimer |
✅ 继承 QObject | UI 控件 | QPushButton / QLabel / QLineEdit |
✅ 继承 QObject | 网络通信 | QNetworkAccessManager |
❌ 不继承 QObject | 数据类 | QString / QByteArray / QList |
❌ 不继承 QObject | 图形类 | QGraphicsItem / QImage / QPixmap |
❌ 不继承 QObject | 几何类 | QPoint / QRect / QSize |
❌ 不继承 QObject | 线程同步 | QMutex / QWaitCondition |
是的,QString
、QByteArray
、QList
等 Qt 容器类和一些值类型 不需要手动 delete
,因为它们采用了 值语义(Value Semantics),不需要显式管理内存。
3. 既然QString不是继承QObject,关于内存管理的一点疑问。
1. 为什么 QString
/ QByteArray
/ QList
不需要 delete
?
这些类都不是指针对象,而是基于 C++ 值语义(Value Semantics),它们的内存管理是 RAII(Resource Acquisition Is Initialization) 风格的。对象离开作用域时,会自动释放内存,不需要 delete
。
✅ 正确示例:普通栈对象
QString str = "Hello, Qt!";
QByteArray data = "12345";
QList<int> list = {1, 2, 3, 4, 5};
// 离开作用域时,它们会自动释放
❌ 错误示例:动态创建但没 delete
(不推荐!)
QString* str = new QString("Hello, Qt!");
// 这样的话,str 不会自动释放,会造成内存泄漏
💡 解决方案:
- 不用
new
,直接用栈对象(推荐!) - 如果必须
new
,要手动delete
QString* str = new QString("Hello, Qt!");
delete str; // 需要手动释放
- 使用智能指针
std::unique_ptr<QString> str = std::make_unique<QString>("Hello, Qt!");
// `std::unique_ptr` 会在作用域结束时自动释放
2. 这些类的内存是如何管理的?
QString
、QByteArray
、QList
等 Qt 容器类,底层使用了隐式共享(Copy-on-Write,COW)技术,使得它们在赋值和拷贝时效率更高。
✅ 隐式共享示例
QString str1 = "Hello";
QString str2 = str1; // 这里不会真的拷贝数据,而是共享同一块内存
str2.append(", Qt!"); // 只有这里修改了数据,Qt 才会创建新的副本
📌 特点:
str2
赋值时,不会立即拷贝数据,而是共享str1
的数据(只增加引用计数)。- 只有当
str2
被修改时,才会触发真正的拷贝,这样提高了性能。
3. QList
内部存储的指针怎么办?
虽然 QList<int>
这样的容器不需要 delete
,但如果 QList
存的是指针,还是要手动释放指针对象**:
✅ 正确示例:存储对象(不需要 delete
)
QList<QString> strList;
strList.append("Item 1");
strList.append("Item 2");
// strList 离开作用域时,会自动释放
❌ 错误示例:存储指针但不释放(会内存泄漏)
QList<MyObject*> objList;
objList.append(new MyObject());
objList.append(new MyObject());
// 这里如果不 `delete`,会内存泄漏!
💡 正确做法:遍历删除指针
for (MyObject* obj : objList) {
delete obj;
}
objList.clear();
或者使用智能指针:
QList<std::unique_ptr<MyObject>> objList;
objList.append(std::make_unique<MyObject>());
objList.append(std::make_unique<MyObject>());
// `unique_ptr` 会自动管理内存,无需手动 `delete`
Qt 类 | 是否继承 QObject | 是否需要 delete | 管理方式 |
---|---|---|---|
QString | ❌ 否 | ❌ 否 | 值语义,自动释放 |
QByteArray | ❌ 否 | ❌ 否 | 值语义,自动释放 |
QList<int> | ❌ 否 | ❌ 否 | 值语义,自动释放 |
QList<MyObject*> | ❌ 否 | ✅ 需要 | 存的是指针,需要手动 delete |
QObject | ✅ 是 | ❌ 否(如果有 parent ) | Qt 父子对象管理 |
QGraphicsItem | ❌ 否 | ✅ 需要 | 由 QGraphicsScene 或手动 delete |
✅ 重点:
- QString / QByteArray / QList 这些是值类型,不需要
delete
,可以直接使用栈变量。 - 如果
QList
里存的是指针,一定要记得delete
,或者使用std::unique_ptr
来自动管理内存。 - Qt 容器类使用了隐式共享(COW),所以赋值不会立即复制数据,只有在修改时才会真正拷贝。