qt智能指针

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_ptrstd::shared_ptr
多线程共享同一对象 使用 QSharedPointerstd::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:无父对象的独立窗口 / 临时对话框

方案 AsetAttribute(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_ptrstd::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_ptrstd::shared_ptr(自动 delete)

3.2记忆口诀

  • 只要是 QWidget 派生,就能显示在屏幕上;

  • 只要是 QObject 派生,就有信号槽和对象树;

  • QWidgetQObject 的子类,所以所有控件也都有信号槽;

  • QImageQString 等,通常不是 QObject,不能用对象树,要用标准智能指针管理。