Qt智能指针QPointer、QSharedPointer、QScopedPointer

一、QPointer

template <typename T>
class QPointer

受保护的指针(A guarded pointer) QPointer<T> 的行为类似于普通的 C++ 指针 T*,但不同的是当所引用的对象被销毁时(普通的 C++ 指针则会变成“悬空指针”),QPointer会自动清除。T 必须是 QObject 的子类。

当需要存储指向由其他人拥有的 QObject 的指针时,受保护的指针非常有用,因为在您仍然持有对它的引用时,该对象可能已被销毁。您可以安全地测试指针的有效性。

QWidget(或 QWidget 的子类)上使用 QPointer 时,QPointer 会在 QObject 析构函数中清除(因为这是在清除 QWeakPointer 对象时进行的操作)。跟踪窗口小部件的任何 QPointers 都不会在 QWidget 析构函数销毁所跟踪的窗口小部件的子项之前被清除。

此外,Qt 还提供了 QSharedPointer,这是一个实现了引用计数共享指针对象的类,可用于维护对单个指针的集合引用。

示例:

    QPointer<QLabel> label = new QLabel;
    label->setText("&Status:");
    ...
    if (label)
        label->show();

如果在此期间删除了QLabel那么 label 变量将保存nullptr而不是无效的地址,最后一行代码将永远不会被执行。

QPointer可用的函数和运算符与普通的非保护指针相同,除了指针算术运算符(+、-、++和--),通常只用于对象数组。

像普通指针一样使用QPointer,您将不需要阅读此类的文档。

要创建保护指针,您可以从T*或相同类型的另一个保护指针构造或赋值给它们。您可以使用operator==()operator!=()将它们相互比较,或使用isNull()测试是否为nullptr。您可以使用*xx->member表示法对它们进行解引用。

保护指针将自动转换为T*,因此您可以自由地混合使用保护和非保护指针。这意味着如果您有一个QPointer<QWidget>,您可以将其传递给需要QWidget*的函数。因此,声明函数以接受QPointer作为参数几乎没有价值;只需使用普通指针即可。在存储指针的时间使用QPointer

请注意,类T必须继承自QObject,否则将导致编译或链接错误。

二、QSharedPointer

template <typename T>
class QSharedPointer

QSharedPointer是C++中的自动共享指针。它在正常情况下的行为与普通指针完全相同,包括对const的尊重。

QSharedPointer对象超出其作用域并且没有其他QSharedPointer对象引用它时,它将删除它所持有的指针。

可以从普通指针、另一个QSharedPointer对象或通过将QWeakPointer对象升级为强引用来创建QSharedPointer对象。

QSharedPointer通过外部引用计数(即放置在对象外部的引用计数器)来持有一个共享指针。正如其名称所示,指针值在QSharedPointerQWeakPointer的所有实例之间共享。然而,指针所指向的对象的内容不应被视为共享:只有一个对象。因此,QSharedPointer不提供分离或复制指向对象的方法。

可选指针跟踪:
QSharedPointer的一个特性是可选的指针跟踪机制,可以在编译时启用以进行调试。当启用时,QSharedPointer在一个全局集合中注册所有跟踪的指针。这样可以捕捉到将同一个指针分配给两个QSharedPointer对象的错误。

在包含QSharedPointer头文件之前,可以通过定义QT_SHAREDPOINTER_TRACK_POINTERS宏来启用该功能。即使在没有启用该功能的代码编译中,也可以安全地使用该特性。QSharedPointer将确保即使在没有指针跟踪的代码编译中,指针也会从跟踪器中移除。

然而,需要注意的是,指针跟踪功能在多重继承或虚继承(即两个不同的指针地址可以引用同一个对象的情况)上有一定的限制。在这种情况下,如果将指针转换为不同类型并且其值发生变化,QSharedPointer的指针跟踪机制可能无法检测到正在跟踪的对象是相同的。

·引用计数器·浅解:
Qt智能指针QSharedPointer 与 C++中的std::shared_ptr其作用是一样的,其应用范围比本文说到的QPointerQScopedPointer更广。

QSharedPointer是一个共享指针,它与 QScopedPointer 一样包装了new操作符在堆上分配的动态对象,但它实现的是引用计数型的智能指针 ,也就是说,与QScopedPointer不同的是,QSharedPointer可以被自由地拷贝和赋值,在任意的地方共享它,所以QSharedPointer也可以用作容器元素。

所谓的计数型指针,就是说在内部QSharedPointer对拥有的内存资源进行引用计数,比如有3个QSharedPointer同时指向一个内存资源,那么就计数3,当引用计数下降到0,那么就自动去释放内存啦。

需要注意的是:QSharedPointer 是线程安全的,因此即使有多个线程同时修改 QSharedPointer 对象也不需要加锁。虽然 QSharedPointer 是线程安全的,但是 QSharedPointer 指向的内存区域可不一定是线程安全的。所以多个线程同时修改 QSharedPointer 指向的数据时还要应该考虑加锁。

QSharedPointer创建与析构顺序的示例:

/* Student类声明 */
class Student : public QObject {
    Q_OBJECT
public:
    Student(QObject * parent = nullptr);
    ~Student();
};

/* Widget类声明 */
class Widget : public QWidget {
    Q_OBJECT
public:
    Widget(QWidget *parent = 0);
    ~Widget();
private:
    QSharedPointer<Student> m_pStudent;
};

#include <QDebug>
/* Widget类实现 */
Widget::Widget(QWidget *parent) : QWidget(parent) {
    qDebug() << __FUNCTION__;
    m_pStudent = QSharedPointer<Student>(new Student());
}

Widget::~Widget() {
    qDebug() << __FUNCTION__;
}

/* Student类实现 */
Student::Student(QObject *parent) : QObject (parent) {
    qDebug() << __FUNCTION__;
}

Student::~Student() {
    qDebug() << __FUNCTION__;
}

运行后「关闭窗口」,输出:

Widget
Student
~Widget
~Student

Qt有一种很好的处理内存管理的方法。核心思想很简单。大多数对象都有一个父对象,当父对象被销毁时,它将首先销毁所有子对象。使用这种技术,您通常可以编写Qt应用程序,几乎不必担心内存管理。通常,您的整个应用程序中实际上不需要一个delete。这非常不错!

示例:下面定义一个‘父’类和一个‘子’类:

class Parent : public QObject {
    Q_OBJECT
public:
    Parent(QObject *parent = 0) : QObject(parent) {
        std::cout << "Parent()" << std::endl;
    }

    virtual ~Parent() {
        std::cout << "~Parent()" << std::endl;
    }
};

class Child : public QObject {
    Q_OBJECT
public:
    Child(QObject *parent = 0) : QObject(parent) {
        std::cout << "Child()" << std::endl;
    }

    virtual ~Child() {
        std::cout << "~Child()" << std::endl;
    }
};

➊正确的调用

void MyAwesomeFunction(Child *o) {
    std::cout << "Using Object!" << std::endl;
}

int main() {
    Parent *const parent = new Parent(0);
    Child *const child = new Child(parent);
    MyAwesomeFunction(child);
    delete parent;
}

输出:

Parent()
Child()
Using Object!
~Parent()
~Child()

➋如果函数MyAwesomeFunction使用QSharedPointer而不是原生指针作参数呢?

void MyAwesomeFunction(const QSharedPointer<Child> &o) {
    std::cout << "Using Object!" << std::endl;
}

int main() {
    Parent *const parent = new Parent(0);
    QSharedPointer<Child> child(new Child(parent));
    MyAwesomeFunction(child);
    delete parent;
}

可能的打印输出如下:

Parent()
Child()
Using Object!
~Parent()
~Child()
QObject: shared QObject was deleted directly. The program is malformed and may crash.

在最糟糕的情况下,它会崩溃。问题是,为什么会出现这种问题?这归结于引用计数和谁在计数。我们在这里有一个由父对象维护的引用计数一个由QSharedPointer维护的引用计数,它们肯定会在何时删除对象上有分歧。这会导致双重释放和使用后释放错误。不好。但事情比这更复杂!Qt内存管理尽最大努力让您进行手动内存管理,同时仍然维护其自己的系统。因此,如果您在删除父对象之前删除子对象,则可以这样做。子对象的析构函数将从其父对象的子对象列表中注销自己。因此,以下代码是良好的:

void MyAwesomeFunction(Child *o) {
    std::cout << "Using Object!" << std::endl;
}

int main() {
    Parent *const parent = new Parent(0);
    Child *const child = new Child(parent);
    MyAwesomeFunction(child);
    delete child;
    delete parent;
}

以上至少让我们基本了解了有什么问题,它涉及到一个QSharedPointer。这是一个开始。解决问题的最简单方法是简单地不混合使用两种内存管理方案。如果需要QSharedPointer,请不要设置parent。像这样:

void MyAwesomeFunction(const QSharedPointer<Child> &o) {
    std::cout << "Using Object!" << std::endl;
}

int main() {
    Parent *const parent = new Parent(0);
    QSharedPointer<Child> child(new Child());
    MyAwesomeFunction(child);
    delete parent;
}

正确的输出:

Parent()
Child()
Using Object!
~Parent()
~Child()

QSharedPointer·自定义删除器·构造函数:

template <typename X, typename Deleter>
QSharedPointer::QSharedPointer(X *ptr, Deleter d)

参数 d 指定了此对象的自定义删除器。当强引用计数降至 0 时,将调用自定义删除器而不是operator delete()

static void doDeleteLater(MyObject *obj) {
    obj->deleteLater();
}

void otherFunction() {
    QSharedPointer<MyObject> obj =
             QSharedPointer<MyObject>(new MyObject, doDeleteLater);
    /* continue using obj */
    obj.clear();// calls obj->deleteLater();
}

也可以直接使用成员函数:

QSharedPointer<MyObject> obj =
        QSharedPointer<MyObject>(new MyObject, &QObject::deleteLater);

三、QScopedPointer

template <typename T, typename Cleanup>
class QScopedPointer
详细描述

手动管理堆分配的对象既困难又容易出错,常见的结果是代码泄漏内存,难以维护。QScopedPointer是一个小型实用程序类,它通过将基于堆栈的内存所有权分配给堆分配(通常称为资源获取即初始化(resource acquisition is initialization:RAII)来大大简化这一过程。

QScopedPointer保证当当前作用域消失时,所指向的对象将被删除。

考虑如一下这个函数,它执行堆分配,并具有各种退出点:

void myFunction(bool useSubClass) {
    MyClass *p = useSubClass ? new MyClass() : new MySubClass;
    QIODevice *device = handsOverOwnership();

    if (m_value > 3) {
        delete p;
        delete device;
        return;
    }

    try {
        process(device);
    } catch (...) {
        delete p;
        delete device;
        throw;
    }

    delete p;
    delete device;
}

这段代码被手动删除调用所拖累。使用QScopedPointer,代码可以简化为:

void myFunction(bool useSubClass) {
    // assuming that MyClass has a virtual destructor
    QScopedPointer<MyClass> p(useSubClass ? new MyClass() : new MySubClass);
    QScopedPointer<QIODevice> device(handsOverOwnership());

    if (m_value > 3)
        return;

    process(device);
}

编译器为QScopedPointer生成的代码与手动编写时的代码相同。使用delete的代码是QScopedPointer使用的候选代码(如果不是,可能是另一种类型的智能指针,如QSharedPointer)。QScopedPointer故意没有复制构造函数或赋值运算符,这样所有权和生存期就可以清楚地交流了。

常规C++指针上的const限定也可以用QScopedPointer表示:

    const QWidget *const p = new QWidget();
    // is equivalent to:
    const QScopedPointer<const QWidget> p(new QWidget());

    QWidget *const p = new QWidget();
    // is equivalent to:
    const QScopedPointer<QWidget> p(new QWidget());

    const QWidget *p = new QWidget();
    // is equivalent to:
    QScopedPointer<const QWidget> p(new QWidget());
Custom Cleanup Handlers(自定义‘删除处理器’)

Arrays as well as pointers that have been allocated with malloc must not be deleted using delete. QScopedPointer's second template parameter can be used for custom cleanup handlers.

The following custom cleanup handlers exist:

  • QScopedPointerDeleter - the default, deletes the pointer using delete
  • QScopedPointerArrayDeleter - deletes the pointer using delete []. Use this handler for pointers that were allocated with new [].
  • QScopedPointerPodDeleter - deletes the pointer using free(). Use this handler for pointers that were allocated with malloc().
  • QScopedPointerDeleteLater - deletes a pointer by calling deleteLater() on it. Use this handler for pointers to QObject's that are actively participating in a QEventLoop.

You can pass your own classes as handlers, provided that they have a public static function static void cleanup(T *pointer)

// this QScopedPointer deletes its data using the delete[] operator:
QScopedPointer<int, QScopedPointerArrayDeleter<int> > arrayPointer(new int[42]);

// this QScopedPointer frees its data using free():
QScopedPointer<int, QScopedPointerPodDeleter> podPointer(reinterpret_cast<int *>(malloc(42)));

// this struct calls "myCustomDeallocator" to delete the pointer
struct ScopedPointerCustomDeleter {
    static inline void cleanup(MyCustomClass *pointer) {
        myCustomDeallocator(pointer);
    }
};

// QScopedPointer using a custom deleter:
QScopedPointer<MyCustomClass, ScopedPointerCustomDeleter> customPointer(new MyCustomClass);
Member Function Documentation

T* QScopedPointer::data()/get() const
Returns the value of the pointer referenced by this object. QScopedPointer still owns the object pointed to.

void QScopedPointer::reset(T* other = nullptr)
Deletes the existing object it is pointing to (if any), and sets its pointer to other. QScopedPointer now owns other and will delete it in its destructor.

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,014评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,796评论 3 386
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,484评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,830评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,946评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,114评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,182评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,927评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,369评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,678评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,832评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,533评论 4 335
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,166评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,885评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,128评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,659评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,738评论 2 351

推荐阅读更多精彩内容