28. 智能指针在Qt中的应用:QPointer与QSharedPointer

Qt 框架里有一套自己的内存管理哲学。说实话,我刚从纯 C++ 转到 Qt 开发时,被它这套机制搞得有点晕。Qt 有父子对象树,有信号槽,还有自己的智能指针家族。今天我们就聊聊 Qt 里两个最常用的智能指针:QPointerQSharedPointer

为什么 Qt 需要自己的智能指针?

标准库的 std::shared_ptr 不好用吗?好用。但 Qt 有个特殊场景——QObject 对象树。你想想看,一个 QWidget 被 delete 了,它的所有子对象也会被自动销毁。这时候如果外面还挂着一个 std::shared_ptr 指向某个子对象,那就出大事了——野指针!

Qt 的解决方案是:QPointerQSharedPointer 分别解决不同的问题。前者是弱引用,后者是带 Qt 特性的共享指针。

QPointer:专为 QObject 设计的“观察者”

QPointer 本质上是一个弱引用。它不增加对象的引用计数,只是默默盯着那个对象。一旦对象被销毁,QPointer 会自动置为 nullptr

我在项目中遇到过这样一个坑:一个对话框里有个 QLabel,我把它指针存到了某个管理器里。对话框关闭后,管理器还在用那个指针更新文本——程序直接崩了。后来换成 QPointer,每次用之前检查一下是否为空,问题就解决了。

核心特点:

  • 只能用于 QObject 及其派生类
  • 不增加引用计数
  • 对象销毁后自动置 nullptr
  • 线程安全?不,不是线程安全的
// 典型用法
QPointer<QLabel> label = new QLabel("Hello");
label->setText("World");

// 假设某处 delete 了 label
delete label;

// 安全检查
if (label) {
    label->show();  // 不会执行到这里
} else {
    qDebug() << "label 已经被销毁了";
}

嗯,这里要注意:QPointer 不能用于非 QObject 类型。你没法写 QPointer<int>,编译器会直接报错。

QSharedPointer:带 Qt 风格的共享指针

QSharedPointerstd::shared_ptr 功能类似,都是引用计数共享所有权。但它有几个 Qt 特有的优势:

  • 支持 QObject 的父子关系:当父对象销毁时,子对象也会被清理
  • 与 Qt 容器无缝集成:比如 QList<QSharedPointer<MyClass>>
  • 支持自定义删除器:而且写法比标准库更简洁

我的建议:如果你在 Qt 项目里需要共享所有权,优先用 QSharedPointer 而不是 std::shared_ptr。原因很简单——它更懂 Qt 的对象生命周期。

// 基本用法
QSharedPointer<MyWidget> sp1(new MyWidget);
QSharedPointer<MyWidget> sp2 = sp1;  // 引用计数 +1

// 自定义删除器
QSharedPointer<MyWidget> sp3(new MyWidget, [](MyWidget* w) {
    qDebug() << "自定义删除:清理资源";
    w->deleteLater();  // Qt 风格的延迟删除
});

QWeakPointer:配合 QSharedPointer 使用

有共享指针,自然就有弱指针。QWeakPointerstd::weak_ptr 对应,用来打破循环引用。不过 Qt 里有个特殊用法——QWeakPointer 可以升级为 QSharedPointer,但前提是对象还活着。

QSharedPointer<Data> sp(new Data);
QWeakPointer<Data> wp = sp;

// 安全地获取共享指针
if (auto locked = wp.toStrongRef()) {
    locked->process();
} else {
    qDebug() << "数据已被释放";
}

QPointer vs QSharedPointer:怎么选?

特性 QPointer QSharedPointer
适用类型 仅 QObject 任意类型
引用计数
自动置空 否(需配合 QWeakPointer)
线程安全 是(引用计数操作)
典型场景 观察 UI 对象是否存活 共享数据所有权

说白了,QPointer 就是个“保安”——它只负责告诉你对象还在不在。QSharedPointer 是个“管家”——它负责管理对象的生命周期。

避坑指南:我曾经踩过的雷

雷区一:QPointer 不是线程安全的。 我曾经在一个工作线程里用 QPointer 检查主线程的 widget,结果偶发崩溃。后来查文档才发现,QPointer 的置空操作不是原子的。多线程场景下,请用 QMetaObject::invokeMethod 或者信号槽来传递指针。

雷区二:别把 QSharedPointer 和原始指针混用。 比如这样:

// 危险写法
QSharedPointer<MyObj> sp(new MyObj);
MyObj* raw = sp.data();
delete raw;  // 双杀!sp 变成野指针

一旦你通过 .data().get() 拿到了原始指针,就要自己保证不会乱 delete。我个人习惯是:除非绝对必要,否则绝不从智能指针里掏原始指针。

雷区三:QSharedPointer 的循环引用。 这个和标准库一样,两个对象互相持有 QSharedPointer,内存就泄漏了。解决方案是用 QWeakPointer 打破循环。

实战:一个简单的 UI 管理器

最后,我写个小例子。假设你有个窗口管理器,需要跟踪所有打开的对话框:

class WindowManager {
public:
    void registerDialog(QSharedPointer<QDialog> dlg) {
        m_dialogs.append(dlg);
    }

    void closeAll() {
        for (auto& dlg : m_dialogs) {
            if (dlg) {
                dlg->close();
            }
        }
        m_dialogs.clear();
    }

private:
    QList<QSharedPointer<QDialog>> m_dialogs;
};

// 使用
auto manager = std::make_unique<WindowManager>();
auto dlg = QSharedPointer<QDialog>(new QDialog);
manager->registerDialog(dlg);

这里用 QSharedPointer 而不是 QPointer,是因为我们需要确保对话框在使用期间不会被意外销毁。如果只是“观察”对话框是否存活,那用 QPointer 更合适。

小结

Qt 的智能指针设计得很务实。QPointer 解决的是 QObject 特有的“自动销毁后通知”问题,QSharedPointer 解决的是通用的共享所有权问题。两者各司其职,用对了地方,代码会干净很多。

我个人在 Qt 项目里的习惯是:

  • UI 对象之间的弱引用 → QPointer
  • 数据模型的共享所有权 → QSharedPointer
  • 需要打破循环引用 → QWeakPointer

记住一点:智能指针是工具,不是银弹。理解 Qt 的对象树生命周期,比盲目套用智能指针更重要。

Qt 智能指针体系 Qt 智能指针 QPointer 特点 仅 QObject · 弱引用 自动置空 · 非线程安全 QSharedPointer 特点 任意类型 · 引用计数 线程安全 · 自定义删除器 QWeakPointer(打破循环引用)