Qt 智能指针详细介绍

2023-10-27

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 作为共享数据对象的基类. 其在内部提供 线程安全的引用计数.
  • 其与 QSharedDataPointerQExplicitlySharedDataPointer 一起使用.
  • 以上三个类都是可重入的.

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;
};

  1. RAII, Resource Acquisition Is Initialization, 资源获取就是初始化. 是 C++ 的一种管理资源, 避免泄漏的惯用方法. 比如, QMutexLocker为了方便管理QMutex的加锁和解锁, 在构造该对象时加锁, 在析构时解锁. ↩︎

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Qt 智能指针详细介绍 的相关文章

  • 如何在Qt无框窗口中实现QSizeGrip?

    如何使用 Qt 无框窗口实现 QSizeGrip 代码会是什么样的 您只需在布局内窗口的一角添加 QSizeGrip 即可使其保持在该角落 QDialog dialog new QDialog 0 Qt FramelessWindowHin
  • 无法在 QGLWidget 中设置所需的 OpenGL 版本

    我正在尝试在 Qt 4 8 2 中使用 QGLWidget 我注意到 QGLWidget 创建的默认上下文不显示 OpenGL 3 1 以上的任何输出 Qt wiki 有一个教程 http qt project org wiki How t
  • 具有多个父项的 Qt 树模型

    我想构建一棵树 其中一个元素可以引用另一个元素 我想要构建的树是 像这样的东西 A B C D E F P this is a pointer to C D first child of C E second child of C I fo
  • SWI-Prolog 与 C++ 接口的问题

    我试图让 SWI Prolog 与 C 很好地配合 现在束手无策 现在 在我开始准确解释我的问题是什么之前 我想首先说明我的项目是关于什么的以及我选择了哪些工具来开发解决方案 我的教授分配给我的任务是开发一个 GUI 程序 作为 SWI p
  • PyQt - 如何从给定的小部件获取顶级父级?

    给定的小部件如何访问它最旧的父级 即顶级小部件 我需要 showMinimized it 现在我正在使用 self parent parent parent showMinimized 但这似乎不是最好的方法 如果它移动了 我需要手动更改父
  • 如何在 QT 中绘制点?

    我正在用 QT 用 C 编写一个应用程序 其中有 n 个点并计算它的凸包 然而 一旦计算出来 我不知道如何绘制点并绘制船体的边界 制作菜单按钮等很简单 但我不确定我是否知道执行此操作的工具 你怎么做到这一点 图形视图 addEllipse
  • 如何使 Python、QT 和 Webkit 在无头服务器上工作?

    我有 Debian Linux 服务器 我用它来做各种事情 我希望它能够完成一些我需要定期完成的网络抓取工作 这段代码可以是在这里找到 http bit ly QeqvzX import sys from PyQt4 QtGui impor
  • Qml 和模糊图像

    我想使用 QML 实现模糊效果 我找到了有关 效果 模糊 的参考资料 例子 http qt gitorious org lscunha qt components lscunha qt components blobs d78feec567
  • 如何在 Qt Creator 中编辑 QtWebKit 的右键上下文菜单?

    好吧 这是我的困境 我正在使用 Qt Creator 制作一个使用 Webkit 的简单应用程序 我认为 Qt Creator 会有一种简单的方法来使用信号和槽编辑器编辑右键单击上下文菜单 但事实证明这不是真的 我知道 webkit 有与上
  • QStyledItemDelegate 绘制自定义小部件失败

    在我的一个项目中 我使用的是QTableWidget为了显示一些复杂的计算结果 为了提高表格的可读性 我需要在单个表格单元格内显示两个对齐的值 后来我想通过使用颜色或箭头等来进一步自定义小部件 为此我源自QStyledItemDelegat
  • Qt 为什么使用 QString::number() 而不是 QLocale().toString()?

    我正在开发的应用程序将在许多国家推出 并且需要支持他们的语言 我一直在回顾我的代码并替换以下每个实例 QString number and QString toDouble with QLocale toString and QLocale
  • 如何在Qt 5中的paintEvent上使用mouseMoveEvent?

    我是 Qt 和 c 的新手 所以我遇到了一些困难 我正在尝试创建一个小部件 它可以获取 mouseMoveEvent 位置并在鼠标位置的像素图上绘制椭圆 下面你可以看到代码 include myimage h include
  • Mac OS X 上的 Qt 字体系列和样式

    我有一个基于 Qt PyQt 的 GUI 应用程序 在 Mac OS X 和其他平台 上运行 它允许用户选择字体并具有粗体和斜体选项的复选框 在更新到新的基于 Cocoa 的 Qt 时 用户发现QFontComboBox不再显示不同的字体样
  • 是否有 Qt 小部件可以浏览应用程序中小部件的层次结构(类似于 Spy++)?

    我们有一个具有复杂的小部件层次结构的应用程序 我希望能够以与 Spy 类似的方式浏览此层次结构 查看和编辑属性 例如大小 如果有一个小部件可以显示此信息 则它不需要在外部应用程序中运行 那么问题来了 这样的神兽存在吗 您可以使用Gammar
  • 连接到 QNetworkReply::error 信号

    我正在使用 Qt5 的新连接语法 QNetworkReply 有一个名为error http qt project org doc qt 5 0 qtnetwork qnetworkreply html error 2还有一个函数叫做err
  • 了解 Qt3D 创建的网格

    我创建了一个 Qt3D 网格 如下所示 Qt3DCore QEntity newEntity new Qt3DCore QEntity Qt3DExtras QConeMesh mesh new Qt3DExtras QConeMesh m
  • 使用 QGraphicsScene 实现流畅的动画

    我希望我的问题并不总是同样的问题 我有一个 QGraphicsScene 它的项目是一些 QGraphicsPixmap 我用一个计时器来移动它们 每秒 SetX 10 我设置 10是因为窗口大100 使用这个解决方案我的动画不流畅 我想我
  • 即使我在单独的线程中运行,QT GUI 也会冻结

    我有一个小型聊天应用程序 其中使用 SQLite 数据库来存储所有对话 我注意到该应用程序随机冻结 然后我必须最小化和最大化它才能使其再次工作 我认为问题可能是 SQLite 选择 插入导致 gui 冻结 我决定尝试将所有 SQLite 方
  • QML 列表视图拖放

    我想创建两个 qml 列表视图 可以执行两个功能 拖放一个列表中的项目以更改项目的顺序 跨列表拖放项目 项目将从一个列表中删除并添加到另一个列表中 根据 Qt 文档中的拖放示例 我决定创建两个访问同一列表模型的列表视图 列表模型中的每个项目
  • 在 Qt 中自动调整标签文本大小 - 奇怪的行为

    在 Qt 中 我有一个复合小部件 它由排列在 QBoxLayouts 内的多个 QLabels 组成 当小部件调整大小时 我希望标签文本缩放以填充标签区域 并且我已经在 resizeEvent 中实现了文本大小的调整 这可行 但似乎发生了某

随机推荐