Qt QSharedPointer用法,10分钟讲清楚

QSharedPointer 是 Qt 提供的一个共享所有权的智能指针 ,用于自动管理动态分配对象的生命周期。它通过引用计数(Reference Counting)机制实现:当最后一个指向对象的 QSharedPointer 被销毁或重置时,对象会被自动 delete。这在 C++ 开发中能有效避免内存泄漏,尤其适合需要多模块共享资源的场景(如流媒体中的缓冲区、视频帧对象等)。

一、核心机制:引用计数

  • 强引用计数:记录当前有多少个 QSharedPointer 指向同一个对象。

  • 弱引用计数(通过 QWeakPointer):不增加强引用计数,仅观察对象是否存在(用于解决循环引用)。

  • 当强引用计数降为 0 时,对象被自动释放。

二、基本用法

1. 创建 QSharedPointer

QSharedPointer 通常通过以下方式创建:

(1)从原始指针构造(需确保指针唯一由 QSharedPointer 管理)
cpp 复制代码
#include <QSharedPointer>
#include <QDebug>

class MyClass {
public:
    MyClass(int v) : value(v) { qDebug() << "MyClass constructed"; }
    ~MyClass() { qDebug() << "MyClass destroyed"; }
    int value;
};

int main() {
    // 从原始指针构造(注意:原始指针必须是通过 new 分配的)
    QSharedPointer<MyClass> ptr(new MyClass(10)); 
    return 0;
}
// 输出:
// MyClass constructed
// MyClass destroyed(main 结束时 ptr 析构,引用计数归零)
(2)使用 QSharedPointer::create()(Qt 5.10+ 推荐)

更安全的方式(避免原始指针暴露):

cpp 复制代码
auto ptr = QSharedPointer<MyClass>::create(20); // 等价于 new MyClass(20),但更安全
(3)拷贝构造/赋值(共享所有权)
cpp 复制代码
QSharedPointer<MyClass> ptr1(new MyClass(30));
QSharedPointer<MyClass> ptr2 = ptr1; // 拷贝构造,引用计数+1(变为2)
ptr1.reset(); // ptr1 放弃所有权,引用计数-1(变为1)
// 此时 ptr2 仍指向对象,main 结束时 ptr2 析构,对象被释放
2. 访问对象成员

通过 operator->operator*访问对象:

cpp 复制代码
QSharedPointer<MyClass> ptr = QSharedPointer<MyClass>::create(40);
qDebug() << ptr->value; // 输出 40(等价于 (*ptr).value)
3. 重置(放弃所有权)
  • reset():释放当前对象的所有权(若引用计数归零则删除对象),并置空。

  • reset(T* other):释放当前对象,接管新对象的所有权。

cpp 复制代码
QSharedPointer<MyClass> ptr(new MyClass(50));
ptr.reset(); // 原对象被删除(输出 "MyClass destroyed"),ptr 变为 nullptr
ptr.reset(new MyClass(60)); // 接管新对象(输出 "MyClass constructed")
4. 判断有效性
  • isNull()/ isEmpty():判断是否为空(无对象)。

  • operator bool():隐式转换为 bool(非空时为 true)。

cpp 复制代码
QSharedPointer<MyClass> ptr;
if (ptr.isNull()) { 
    qDebug() << "ptr is null";
}

if (ptr) { // 等价于 !ptr.isNull()
    qDebug() << "ptr is not null";
}
5. 获取原始指针
  • data()get():返回原始指针(谨慎使用,避免外部手动 delete)。
cpp 复制代码
MyClass* rawPtr = ptr.data();
rawPtr->value = 100; // 直接修改对象(不推荐,破坏封装)

三、高级用法

1. 自定义删除器(Deleter)

默认情况下,QSharedPointer 使用 delete释放对象。若对象通过其他方式分配(如 mallocfopen打开的文件),可自定义删除器。

**示例:管理 FILE资源*

cpp 复制代码
#include <cstdio>

// 自定义删除器:关闭文件
void fileCloser(FILE* fp) {
    if (fp) fclose(fp);
    qDebug() << "File closed";
}

int main() {
    // 用 lambda 作为删除器(更灵活)
    QSharedPointer<FILE> filePtr(fopen("test.txt", "r"), [](FILE* fp) {
        if (fp) fclose(fp);
        qDebug() << "Custom deleter: File closed";
    });
    
    if (filePtr) {
        char buf[100];
        fgets(buf, sizeof(buf), filePtr.data());
        qDebug() << "Read:" << buf;
    }
    // 离开作用域时,filePtr 析构,调用自定义删除器关闭文件
    return 0;
}
2. 与 QWeakPointer 配合(解决循环引用)

当两个对象互相持有 QSharedPointer 时,会导致循环引用(引用计数无法归零,内存泄漏)。此时需用 QWeakPointer 打破循环(弱引用不增加强引用计数)。

示例:循环引用问题

cpp 复制代码
class B;

class A {
public:
    QSharedPointer<B> bPtr;
    ~A() { qDebug() << "A destroyed"; }
};

class B {
public:
    QSharedPointer<A> aPtr; // 循环引用!A 和 B 互相强引用
    ~B() { qDebug() << "B destroyed"; }
};

int main() {
    QSharedPointer<A> a(new A);
    QSharedPointer<B> b(new B);
    a->bPtr = b; // a 强引用 b(b 的引用计数=2)
    b->aPtr = a; // b 强引用 a(a 的引用计数=2)
    // 离开作用域时,a 和 b 的引用计数各减1(变为1),不会触发析构!
    return 0;
}
// 输出:(无析构信息,内存泄漏)

解决方案:用 QWeakPointer 替代一方的强引用

cpp 复制代码
class B;

class A {
public:
    QSharedPointer<B> bPtr;
    ~A() { qDebug() << "A destroyed"; }
};

class B {
public:
    QWeakPointer<A> aPtr; // 弱引用,不增加 a 的强引用计数
    ~B() { qDebug() << "B destroyed"; }
};

int main() {
    QSharedPointer<A> a(new A);
    QSharedPointer<B> b(new B);
    a->bPtr = b; // b 的引用计数=2(a->bPtr 和 b 本身)
    b->aPtr = a; // a 的引用计数仍为1(仅 a 本身)
    
    // 离开作用域时:
    // a 析构 → a 的引用计数-1(变为0)→ 调用 A 的析构 → a->bPtr 析构 → b 的引用计数-1(变为1)
    // b 析构 → b 的引用计数-1(变为0)→ 调用 B 的析构 → b->aPtr 是弱引用,无影响
    return 0;
}
// 输出:
// A destroyed
// B destroyed

四、注意事项

  1. 避免混合管理:不要用多个独立智能指针(如 QSharedPointer 和 std::shared_ptr)管理同一原始指针,会导致重复释放。

  2. 线程安全 :QSharedPointer 的引用计数操作是线程安全的(原子操作),但对象本身的访问仍需同步(如多线程读写对象成员)。

  3. 与 QObject 的配合 :QObject 自带父子内存管理机制(deleteLater),若用 QSharedPointer 管理 QObject 派生类,需注意:

    • 避免同时设置父对象和 QSharedPointer(可能导致双重释放)。

    • 推荐用 QSharedPointer 管理无父对象的 QObject,或用 QObject::setParent(nullptr)解除父子关系。

  4. 不支持数组 :QSharedPointer 默认用 delete释放对象,若需管理数组(new T[]),应使用 QSharedPointer<T[]>(Qt 5.6+),它会调用 delete[]

五、总结

QSharedPointer 是 Qt 中管理动态对象的核心工具,通过引用计数自动释放资源,适合多模块共享对象的场景。关键要点:

  • 优先用 QSharedPointer<T>::create()构造,避免原始指针暴露。

  • 复杂资源管理用自定义删除器

  • 循环引用用 QWeakPointer​ 解决。

  • 注意线程安全和与 QObject 的配合。

相关推荐
月落归舟1 小时前
深入理解Java适配器模式,彻底搞懂设计思想
java·开发语言·适配器模式
Mr_pyx1 小时前
【LeetHOT100】二叉树的中序遍历——Java多解法详解
java·开发语言·深度优先
m0_738120722 小时前
渗透测试——Djinn1靶场详细渗透提权过程讲解(绕过黑名单限制,命令执行反弹shell,pyc反编译,代码白盒分析,python沙盒逃逸)
开发语言·python·php
web守墓人2 小时前
【go语言】go语言实现go-torch, 完成Lenet-5的搭建,训练,以及pth和onnx模型导出
开发语言·后端·golang
TEC_INO2 小时前
Linux50:ROCKX+RV1126视频流检测人脸
开发语言·前端·javascript
平凡但不平庸的码农2 小时前
Go 语言常用标准库详解
开发语言·后端·golang
下载居2 小时前
Node.js(Javascript运行环境) 26.1
开发语言·javascript·node.js
范什么特西3 小时前
第一个Mybatis
java·开发语言·mybatis
超梦dasgg3 小时前
智慧充电系统计费定价服务Java 实现
java·开发语言·spring·微服务