深入理解Qt 智能指针
作者:zhaoyongCNSX
智能指针是一种特殊的指针,可以自行管理和释放资源,防止内存泄漏和悬挂指针,本文主要介绍了深入理解Qt 智能指针,具有一定的参考价值,感兴趣的可以了解一下
1. Qt智能指针概述
- Qt 提供了一套基于父子对象的内存管理机制, 所以我们很少需要去手动 delete. 但程序中不一定所有类都是QObject的子类, 这种情况下仍然需要使用一些智能指针.
- 注意: 在 Qt 中使用智能指针时, 一定要避免发生多次析构.
2. Qt中的智能指针分类
根据不同的使用场景, 可分为以下几种:
- 共享数据. 隐式或显式的共享数据(不共享指针), 也被称为 侵入式指针.
QSharedDataPointer
指向隐式共享对象的指针.QExplicitlySharedDataPointer
指向显式共享对象的指针.
- 共享指针. 线程安全.
QSharedPointer
. 有点像std::shared_ptr
,boost::shared_ptr
. 维护引用计数, 使用上最像原生指针.QWeakPointer
, 类似于boost::weak_ptr
. 作为QSharedPointer
的助手使用. 未重载*
和->
. 用于解决强引用形成的相互引用.
- 范围指针. 为了RAII1目的, 维护指针所有权, 并保证其在超出作用域后恰当的被销毁, 非共享.
QScopedPointer
. 相当于std::unique_ptr
.- 所有权唯一, 其拷贝和赋值操作均为私有. 无法用于容器中.
QScopedArrayPointer
- 追踪给定 QObject 对象生命, 并在其析构时自动设置为 NULL.
QPointer
.
3. 共享数据
- 共享数据是为了实现 “读时共享, 写时复制”. 其本质上是延迟了 执行深拷贝 的时机到了需要修改其值的时候.
- C++实现为 在拷贝构造和赋值运算符函数中不直接深度拷贝, 而是维护一个引用计数并获得一个引用或指针. 在需要改变值的方法中再执行深度拷贝.
- 隐式共享为, 我们无需管理深度拷贝的时机, 它会自动执行.
- 显式共享为, 我们需要人为判断什么时候需要深度拷贝, 并手动执行拷贝.
QSharedData
作为共享数据对象的基类. 其在内部提供 线程安全的引用计数.- 其与
QSharedDataPointer
和QExplicitlySharedDataPointer
一起使用. - 以上三个类都是可重入的.
3.1. 隐式共享
QSharedDataPointer
表示指向隐式共享对象的指针.- 其在写操作时, 会自动调用
detach()
. 该函数在当共享数据对象引用计数大于1, 会执行深拷贝, 并将该指针指向新拷贝内容. (这是在该类的非 const 成员函数中自动调用的, 我们使用时不需要关心).
比如, 我们现在有一个类 MyClass, 现在要将其改造成支持隐式共享的类.
#if 1 // MyClass 原始版本 class MyClass { public: MyClass(){} MyClass(const MyClass &other) { m_id = other.GetId(); m_path = other.GetPath(); } ~MyClass(){} int GetId() const { return m_id; } void SetId(int val) { m_id = val; } QString GetPath() const { return m_path; } void SetPath(QString val) { m_path = val; } private: int m_id = -1; QString m_path; }; #else // MyClass 支持隐式共享 // 1. 将 MyClass 的所有数据成员都放到 MyClassData 中. // 2. 在 MyClass 中维护一个 QSharedDataPointer<MyClassData> d. // 3. MyClass 中通过 d-> 的形式访问数据. // 4. MyClassData 继承自 QSharedData. #include <QSharedData> #include <QSharedDataPointer> class MyClassData : public QSharedData { public: MyClassData(){} MyClassData(const MyClassData &other) : QSharedData(other), id(other.id), path(other.path) {} ~MyClassData(){} int id = -1; QString path; }; class MyClass { public: MyClass(){ d = new MyClassData(); } MyClass(int id, const QString & path) { d = new MyClassData(); SetId(id); SetPath(path); } MyClass(const MyClass &other) : d(other.d){} ~MyClass(){} int GetId() const { return d->id; } void SetId(int val) { d->id = val; } QString GetPath() const { return d->path; } void SetPath(QString val) { d->path = val; } private: QSharedDataPointer<MyClassData> d; }; #endif
3.2. 显式共享
QExplicitlySharedDataPointer
表示指向显式共享对象的指针.- 与
QSharedDataPointer
不同的地方在于, 它不会在非const成员函数执行写操作时, 自动调用detach()
. - 所以需要我们在写操作时, 手动调用 detach().
- 它的行为很像 C++ 常规指针, 不过比指针好的地方在于, 它也维护一套引用计数, 当引用计数为0时会自动设置为 NULL. 避免了悬空指针的危害.
修改上面例子中用到的 QSharedDataPointer 为 QExplicitlySharedDataPointer.
注意: 如果在使用时发现所有写操作的函数中都调用了 detach(), 那就可以直接使用 QSharedDataPointer 了.
#include <QExplicitlySharedDataPointer> class MyClass { public: MyClass(){ d = new MyClassData(); } MyClass(int id, const QString & path) { d = new MyClassData(); SetId(id); SetPath(path); } MyClass(const MyClass &other) : d(other.d){} ~MyClass(){} int GetId() const { return d->id; } void SetId(int val) { // 需要手动调用 detach() d.detach(); d->id = val; } QString GetPath() const { return d->path; } void SetPath(QString val) { // 需要手动调用 detach() d.detach(); d->path = val; } private: QExplicitlySharedDataPointer<MyClassData> d; };
4. 共享指针
4.1. QSharedPointer
- 可用于容器中.
- 可提供自定义 Deleter, 所以可用于
delete []
的场景. - 线程安全. 多线程同时修改其对象无需加锁. 但其指向的内存不一定线程安全, 所以多线程同时修改其指向的数据, 还需要加锁.
// 指定 Deleter static void doDeleteLater(MyObject *obj) { obj->deleteLater(); } void TestSPtr() { QSharedPointer<MyObject> obj(new MyObject, doDeleteLater); // 调用 clear 清除引用计数, 并调用 Deleter 删除指针对象. // 此处将调用 doDeleteLater obj.clear(); QSharedPointer<MyObject> pObj1 = obj; pObj1->show(); if (pObj1) { } } /*------------- 实现单例 -------------*/ // cpp 中定义全局变量 QSharedPointer<MyObject> g_ptrMyObj; QSharedPointer<MyObject> GetMyObj() { if (g_ptrMyObj.data() == NULL) { g_ptrMyObj.reset(new MyObject()); } return g_ptrMyObj; }
4.2. QWeakPointer
- 创建: 只能通过 QSharedPointer 的赋值来创建.
- 使用: 不可直接使用. 没有重载
*
和->
. 需要用toStrongRef()
转换为 QSharedPointer, 并判断是否为 NULL 之后再使用.- 可使用
data()
取到指针值, 但不确保其有效. 如果想使用该值, 需用户在外部使用其他手段保证其指针值有效. - 曾经有一段时间, Qt 官方想用 QWeakPointer 取代 QPointer, 但在 Qt5 重写了 QPointer 后, 就不再这么建议了.
- 所以使用 QWeakPointer 的最佳场景仍然是: 作为 QSharedPointer 的助手类使用.
- 可使用
打开宏 TEST_memory_will_leak, 则因为产生循环引用, 导致内存泄漏, 表现为: 不会打印 “destruct A”, “destruct B”
关闭宏, 使用 QWeakPointer, 不会产生内存泄漏.
#include <QSharedPointer> #include <QWeakPointer> class A; class B; #define TEST_memory_will_leak class A { public: ~A() { qDebug() << "destruct A"; } #ifdef TEST_memory_will_leak QSharedPointer<B> ptr_B; #else QWeakPointer<B> ptr_B; #endif //TEST_memory_will_leak }; class B { public: ~B() { qDebug() << "destruct B"; } #ifdef TEST_memory_will_leak QSharedPointer<A> ptr_A; #else QWeakPointer<A> ptr_A; #endif //TEST_memory_will_leak }; int main() { QSharedPointer<A> nA(new A()); QSharedPointer<B> nB(new B()); // 若内部使用 QSharedPointer, 则此处会形成循环引用. nA->ptr_B = nB; nB->ptr_A = nA; #ifdef TEST_memory_will_leak if (!nA->ptr_B.isNull()) { qDebug() << "use shared ptr"; } #else if (!nA->ptr_B.toStrongRef().isNull()) { qDebug() << "use weak ptr"; } #endif TEST_memory_will_leak }
5. 范围指针
void main() { { QScopedPointer<MyClass> p(new MyClass()); p->func(); } // 退出作用域后析构 { QScopedArrayPointer<int> p(new int[10] ); p[1] = 10; } // 退出作用域后析构 }
6. 追踪特定QObject对象生命
[***以下描述可搜索 DevBean 的 continue-using-qpointer 一文获取更详细信息***].
- QPointer 在某几个 Qt5 版本中, 被标注为废弃. 且打算使用 QWeakPointer 来代替其原有功能. (为此还允许 QWeakPointer 可独立于 QSharedPointer 使用, 并增加了一系列接口, 引发了接口歧义的副作用). 但经过对 QPointer 的重写后, 解决了之前性能问题, 所以便移除了废弃标志, 并取消了 QWeakPointer 独立使用的相关描述.
- 之后还是继续将 QWeakPointer 和 QSharedPointer 捆绑使用. 并继续愉快的使用 QPointer 吧.
class MyHelper{ public: MyHelper(QPushButton *btn) : m_btn(btn) , m_btn2(NULL) {} void SetBtn(QPushButton *btn) { m_btn2 = btn; } void FuncShow() { // 当外部的 QPushButton 析构后, 该值自动设置为 NULL if (m_btn) { m_btn->show(); } if (m_btn2) { m_btn2->show(); } } private: QPointer<QPushButton> m_btn; QPointer<QPushButton> m_btn2; };
RAII, Resource Acquisition Is Initialization, 资源获取就是初始化. 是 C++ 的一种管理资源, 避免泄漏的惯用方法. 比如, QMutexLocker
为了方便管理QMutex
的加锁和解锁, 在构造该对象时加锁, 在析构时解锁.
到此这篇关于深入理解Qt 智能指针的文章就介绍到这了,更多相关Qt 智能指针内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!