1.前置知识点
1.1为什么需要智能指针
在 C++ 中,用 new 分配的内存必须用 delete 释放,否则就会内存泄漏 。
Qt 的对象又比较特殊:多数 QObject 子类(包括 QWidget)可以形成对象树 ,父对象销毁时会自动 delete 子对象,这已经是一种自动管理机制。
但仍有大量场景(如独立窗口、非 QObject 数据)需要手动释放,这时智能指针就派上了用场------它帮你自动释放,避免遗忘。
1.2c++库标准指针
1.2.1 std::unique_ptr(独占所有权)
-
特点 :同一时间只有一个
unique_ptr拥有对象,不能拷贝,只能移动。多数情况使用 -
对于qt对象,需要自定义删除器,因为qt中推荐使用
deleteLater()安全删除,直接delete可能引发崩溃。自定义删除器就是告诉unique_ptr:"不要用delete,用我这个函数来销毁对象"。
使用实例
// 自定义删除器:关闭窗口并安全删除
auto deleter = [](QQuickWidget* w) {
if (w) {
w->close();
w->deleteLater();
}
};
// 使用情况1:创建一个 unique_ptr,传入原始指针和删除器
std::unique_ptr<QQuickWidget, decltype(deleter)> m_widget(new QQuickWidget, deleter);
// 使用情况2:或者先构造空指针,再用 reset 接管
std::unique_ptr<QQuickWidget, decltype(deleter)> m_widget(nullptr, deleter);
m_widget.reset(new QQuickWidget);
// 当 m_widget 销毁或 reset 时,自动调用 deleter
1.2.2std::shared_ptr(共享所有权)
-
特点 :多个
shared_ptr共享同一个对象,内部有引用计数,计数归零时自动释放。 -
适用场景:多个所有者需要共享同一份数据,且所有者生命周期不确定。
使用实例
auto shared = std::make_shared<int>(42);
std::shared_ptr<int> another = shared; // 引用计数变为2
// 所有 shared_ptr 销毁时自动 delete 对象
1.2.3std::weak_ptr(弱引用)
-
配合
shared_ptr使用,不增加引用计数,用于打破循环引用。 -
可以通过
lock()方法临时提升为shared_ptr,如果对象已销毁则返回空。
使用实例
std::weak_ptr<int> weak = shared;
if (auto sp = weak.lock()) {
// 对象还存在,sp 是 shared_ptr
}
1.3Qt专有智能指针
1.3.1QPointer<T>(弱引用,自动感知对象销毁,主要用于判断对象是否为空)
-
只能用于
QObject及其子类。 -
不管理生命周期 ,只是包装一个指针。当所指对象被销毁后,
QPointer自动变为nullptr。 -
你需要自己释放对象 (设置
WA_DeleteOnClose或手动deleteLater())。
使用实例
QPointer<QLabel> label = new QLabel("Hello");
// 如果 label 在其他地方被 deleteLater 了,label 自动变 nullptr
if (label) {
// 安全使用
}
// 注意:label 的释放仍需自己管理,比如:
label->setAttribute(Qt::WA_DeleteOnClose);
// 或者合适时机 label->deleteLater();
1.3.2QScopedPointer<T>(独占,轻量)
-
类似
std::unique_ptr,但不可移动(C++11 前就存在)。 -
离开作用域时自动
delete对象。 -
默认不支持
deleteLater(),如果需要,可以像unique_ptr一样自定义删除器(通过模板参数QScopedPointer<T, QScopedPointerDeleter<T>>,但语法较繁琐)。
1.3.3QSharedPointer<T> 和 QWeakPointer<T>
-
与
std::shared_ptr/std::weak_ptr类似,但内部使用 Qt 的原子操作,线程安全。 -
可以和 Qt 的信号槽、容器协同更顺畅。
2.使用场景
| 场景 | 推荐做法 |
|---|---|
| 有父对象的 QWidget | 不进行任何手动删除,不使用智能指针。父对象销毁时自动清理子对象。 |
| 无父对象的顶层窗口 | 使用 setAttribute(Qt::WA_DeleteOnClose) 让窗口关闭时自动释放;或用 std::unique_ptr + 自定义删除器(deleteLater())。 |
| 需要监控某个 QObject 是否存在 | 使用 QPointer,它会在对象销毁后自动变空。 |
| 非 QObject 的普通数据 | 优先用 std::unique_ptr 或 std::shared_ptr。 |
| 多线程共享同一对象 | 使用 QSharedPointer 或 std::shared_ptr(保证线程安全)。 |
| 禁止混用 | 不要既让 Qt 对象树管理,又用智能指针去释放,双重释放会崩溃。 |
2.1典型场景1:有父对象(parent)的 QWidget / QObject
原则 :什么都不要用,连裸指针都不用 delete。Qt 的对象树会自动删除子对象,你手动删除反而双重释放崩溃。
// 在 MainView 构造函数中
auto* btn = new QPushButton("click me", this); // this 是父窗口
// 不需要 delete,MainView 销毁时会自动删除 btn
2.2典型场景2:无父对象的独立窗口 / 临时对话框
方案 A :setAttribute(Qt::WA_DeleteOnClose)(最省事)
适用:一次性的窗口,关闭即销毁。
void MainView::openToolWindow() {
auto* w = new QWidget; // 无父对象
w->setAttribute(Qt::WA_DeleteOnClose);
w->show();
// 用户关闭窗口时,Qt 自动调用 w->deleteLater(),安全释放
}
方案 B:std::unique_ptr + 自定义删除器
优势 :reset() 或 unique_ptr 销毁时自动执行删除器,保证即使忘记手动释放也不会泄漏。
// 自定义删除器:关闭窗口并用 deleteLater() 安全删除
auto deleter = [](QQuickWidget* w) {
if (w) {
w->close();
w->deleteLater();
}
};
// 成员变量
std::unique_ptr<QQuickWidget, decltype(deleter)> m_skinDialog{nullptr, deleter};
// 打开对话框
void MainView::skin_change() {
m_skinDialog.reset(new QQuickWidget); // 旧窗口自动释放
m_skinDialog->setWindowFlags(Qt::Dialog | Qt::FramelessWindowHint);
m_skinDialog->setSource(QUrl("qrc:/qml/SkinSelect.qml"));
m_skinDialog->show();
}
2.3典型场景3:需要"监视"某个 QObject 是否存活
QPointer<T>,它不会管理生命周期,只是能自动感知对象被销毁后变为 nullptr,释放对象仍然要自己做(比如设置 WA_DeleteOnClose 或调用 deleteLater())。
场景:你有一个独立的工具窗口,可能需要判断它是否还打开。
QPointer<QWidget> m_toolWindow;
void MainView::openTool() {
if (m_toolWindow) {
m_toolWindow->close(); // 关闭旧窗口(如果设置了 WA_DeleteOnClose,会自动释放)
}
auto* w = new QWidget;
w->setAttribute(Qt::WA_DeleteOnClose);
m_toolWindow = w; // QPointer 自动跟踪 w
w->show();
}
void MainView::someFunction() {
if (m_toolWindow) {
// 窗口还开着,可以安全操作
}
}
2.4典型场景4:非 QObject 的普通数据
直接用 std::unique_ptr 或 std::shared_ptr,它们会调用 delete,无需自定义删除器。
class DataManager {
std::unique_ptr<QImage> m_image; // 独占图片数据
public:
void load(const QString& path) {
m_image = std::make_unique<QImage>(path);
}
// 析构时自动释放图片内存
};
2.5典型场景5:极少数情况,多个对象共享同一份数据**(无父对象,如全局配置、共享服务)**
#include <QSharedPointer>
#include <QDebug>
// 一个普通的 QObject 派生类,无父对象
class MyService : public QObject {
Q_OBJECT
public:
MyService() { qDebug() << "MyService created"; }
~MyService() { qDebug() << "MyService destroyed"; }
void doWork() { qDebug() << "doing work"; }
};
void demo1() {
//定义一个函数,用于安全释放
auto deleter = [](MyService* obj) {
qDebug() << "Deleter called, using deleteLater()";
obj->deleteLater();
};
QSharedPointer<MyService> ptr1(new MyService, deleter);// 此时引用计数 = 1
{
// 2. 通过拷贝构造或赋值,创建第二个 QSharedPointer
QSharedPointer<MyService> ptr2 = ptr1;
// 引用计数 = 2
// 两个指针都能访问同一个对象
ptr1->doWork();
ptr2->doWork();
} // ptr2 离开作用域,引用计数 -1,变为 1
// 3. 即使 ptr2 销毁了,对象依然存在,因为 ptr1 还持有它
ptr1->doWork();
} // ptr1 离开作用域,引用计数 -1,变为 0,自动 delete MyService 对象
输出:
MyService created
doing work
doing work
doing work
MyService destroyed
3.补充
3.1如何根据继承关系选择内存管理方式
| 类别 | 典型例子 | 管理方式 |
|---|---|---|
| QWidget 派生,有父对象 | new QPushButton(this) |
什么都不用,父对象销毁时自动删除子控件 |
| QWidget 派生,无父对象(独立窗口) | new QWidget(无 parent) |
1. 设置 WA_DeleteOnClose 2. std::unique_ptr + 自定义删除器调用 deleteLater() |
| 非可视 QObject,有父对象 | new QTimer(this) |
与有父对象 QWidget 相同,父对象自动删除 |
| 非可视 QObject,无父对象 | new QNetworkAccessManager |
1. 手动 deleteLater() 2. std::unique_ptr + 删除器 3. QSharedPointer(若需共享) |
| 非 QObject 类(动态分配) | new QImage() |
std::unique_ptr 或 std::shared_ptr(自动 delete) |
3.2记忆口诀
-
只要是
QWidget派生,就能显示在屏幕上; -
只要是
QObject派生,就有信号槽和对象树; -
QWidget是QObject的子类,所以所有控件也都有信号槽; -
QImage、QString等,通常不是 QObject,不能用对象树,要用标准智能指针管理。