美文网首页悟透Qt
Qt智能指针QPointer、QSharedPointer、QS

Qt智能指针QPointer、QSharedPointer、QS

作者: akuan | 来源:发表于2023-08-07 05:08 被阅读0次

    一、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.

    相关文章

      网友评论

        本文标题:Qt智能指针QPointer、QSharedPointer、QS

        本文链接:https://www.haomeiwen.com/subject/jozwpdtx.html