Introduction to Qt smart pointers: QSharedPointer, QWeakPointer, QScopedPointer, QPointer (with examples)

1 Overview

In the case of dynamic memory allocation, it is necessary to ensure that the ownership of objects is managed and transferred correctly. Using smart pointers can help us automatically manage the life cycle and ownership of objects, avoiding memory leaks and dangling pointers.

♦ When do you need to use smart pointers?

  • In the case of using QObject objects, it is necessary to ensure that object life cycles and parent-child relationships are managed correctly. The life cycle of QObject objects is affected by the parent-child relationship, so smart pointers such as QPointer need to be used to manage the pointers of QObject objects.

  • In multi-threaded programming, it is necessary to ensure that no race conditions occur when multiple threads access shared objects. Using smart pointers can avoid race conditions because it automatically counts references to objects, ensuring that objects are not deleted until all references are released

  • When using exception handling, you need to ensure that all dynamically allocated objects are properly deleted when the function returns. Using smart pointers ensures that all dynamically allocated objects are properly deleted when the function returns to avoid memory leaks.

  • Prevent forgetting to call delete

In short, when we need to dynamically allocate memory and need to ensure correct management of object life cycle, ownership, thread safety, etc., we can consider using smart pointers.

2. How many smart pointers are there in Qt?

Commonly used ones include QSharedPointer, QWeakPointer, QScopedPointer and QPointer;

  • QSharedPointer for cases of shared ownership
  • QWeakPointer is for when you don't share ownership but need to access objects managed by QSharedPointer
  • QScopedPointer is suitable for cases of exclusive ownership.
  • QPointer is mainly used to solve the problem of dangling pointers, and is suitable for references between Qt objects

    The details are as follows:

2.1 QSharedPointer instance

QSharedPointer: A strong reference, roughly equivalent to shared_ptr in the C++11 standard, used to manage the shared ownership of dynamically allocated objects, that is, multiple QSharedPointer objects can point to the same object and share the memory management of the object.

It uses reference counting to track object usage, and when the last QSharedPointer object is destroyed, it will automatically release the object's memory. Due to the use of reference counting, QSharedPointer can automatically handle the life cycle of the pointer, avoiding problems such as memory leaks and dangling pointers, so it is the most commonly used smart pointer in Qt.

#include <QCoreApplication>
#include <QSharedPointer>
#include <QDebug>

class MyClass
{
    
    
public:
    MyClass(int value) : m_value(value) {
    
    }
    void setValue(int value) {
    
     m_value = value; }
    int getValue() const {
    
     return m_value; }
private:
    int m_value;
};

int main()
{
    
    
    QSharedPointer<MyClass> pointer1(new MyClass(10));  // 引用计数为1
    {
    
    
        QSharedPointer<MyClass> pointer2 = pointer1;    // 引用计数为2
    }                                                   // pointer2销毁,引用计数为1
    qDebug() << "Value:" << pointer1->getValue();       // 输出10
    {
    
    
        QSharedPointer<MyClass> pointer3 = pointer1;    // 引用计数为2
        
        pointer1.clear();                               // 引用计数为1
        qDebug() << "Value:" << pointer3->getValue();   // 输出10
        
		QWeakPointer<MyClass> weak(pointer3);
    }                                                   // pointer3销毁,引用计数为0,MyClass对象被自动释放
    return 0;
}

Output result:
insert image description here

It should be noted that QSharedPointer can only manage the memory of dynamically allocated objects. If we use it to point to a stack object or a global object, then it will not automatically free the memory of the object, which may cause a program crash or memory leak.

In-depth understanding: C++ smart pointer (shared_ptr/weak_ptr) source code analysis

2.2 QSharedPointer and QWeakPointer instances

QWeakPointer: Weak reference, roughly equivalent to weak_ptr in the C++11 standard, used to access the object pointed to by QSharedPointer without sharing ownership. QWeakPointer points to the object managed by QSharedPointer, but it will not increase the reference count of the object, nor will it affect the life cycle of the object. When the object is released, QWeakPointer will be automatically set to a null pointer, avoiding the problem of dangling pointers.

QWeakPointer is a smart pointer introduced to cooperate with QSharedPointer. It is more like an assistant of QSharedPointer, observing resource usage like a bystander.

#include <QSharedPointer>
#include <QWeakPointer>
#include <QDebug>

class MyClass
{
    
    
public:
    MyClass(int value) : m_value(value) {
    
    
        qDebug() << "MyClass constructor called with value" << m_value;
    }
    ~MyClass() {
    
    
        qDebug() << "MyClass destructor called with value" << m_value;
    }
    int getValue() const {
    
    
        return m_value;
    }

private:
    int m_value;
};

int main()
{
    
    
    QSharedPointer<MyClass> shared(new MyClass(20));
    QWeakPointer<MyClass> weak(shared);

    qDebug() << "Shared pointer value:" << shared->getValue();
    qDebug() << "Weak pointer value:" << weak.data()->getValue();

    shared.clear();

    if (weak.isNull()) {
    
    
        qDebug() << "Weak pointer is null - object has been deleted"; //执行
    } else {
    
    
        qDebug() << "Weak pointer is not null - object still exists";
    }

    return 0;
}

Print result:

insert image description here
In the example, a QSharedPointer object and a QWeakPointer object are created, and they both point to a MyClass object. At the end of the main function, use shared.clear() to release ownership of the MyClass object. At this time, the reference count of the MyClass object is 0 and will be automatically deleted, and at this time the QWeakPointer object weak is also null.

In actual use, it can be repackaged:

typedef QSharedPointer<MyClass>  SharedMyClassPtr;
typedef QWeakPointer<MyClass>    WeakMyClassPtr;

2.3 QScopedPointer instance

QScopedPointer is similar to unique_ptr in C++11, which is used to manage the exclusive ownership of dynamically allocated objects, that is, only one QScopedPointer can point to the object at a time.

QScopedPointer uses RAII (resource acquisition is initialization) technology. When QScopedPointer is destroyed, it will automatically release the memory of the managed object. QScopedPointer does not support copy and assignment operations, this is to avoid the problem of sharing ownership among multiple pointers. If you need to transfer ownership between multiple pointers, you should use QSharedPointer or QWeakPointer.

Here is a simple example:

#include <QScopedPointer>
#include <QDebug>

class MyClass
{
    
    
public:
    MyClass(int value) : m_value(value) {
    
    
        qDebug() << "MyClass constructor called with value" << m_value;
    }
    ~MyClass() {
    
    
        qDebug() << "MyClass destructor called with value" << m_value;
    }
    int getValue() const {
    
    
        return m_value;
    }

private:
    int m_value;
};

int main()
{
    
    
    QScopedPointer<MyClass> myObject(new MyClass(23));

    qDebug() << "My object value:" << myObject->getValue();

    return 0;
}

The result is as follows:

insert image description here

2.4 QPointer instances

Since the life cycle of QObject objects is affected by the parent-child relationship, when the parent object is deleted, all child objects will also be deleted. Using QPointer can avoid the problem that when the parent object is deleted, the pointer of the child object points to the object that has been deleted.

Usually, when we manually delete a pointer, we need to empty it again, otherwise it will become a dangling wild pointer, then QPointer is to help with this, and it will be automatically set to NULL

♦ Scenario 1:

//
// myqpointer.cpp
//
#include "myqpointer.h"

MyQPointer::MyQPointer(QObject *parent) : QObject(parent)
{
    
    
    qDebug() << "MyObject constructor called";
}

MyQPointer::~MyQPointer()
{
    
    
    qDebug() << "MyObject destructor called";
}

void MyQPointer::doSomething()
{
    
    
    qDebug() << "MyObject is doing something";
}

//
// myqpointer.h
//
#ifndef MYQPOINTER_H
#define MYQPOINTER_H

#include <QObject>
#include <QPointer>
#include <QDebug>

class MyQPointer : public QObject
{
    
    
    Q_OBJECT
public:
    explicit MyQPointer(QObject *parent = nullptr);
    ~MyQPointer();
    void doSomething();
};
#endif // MYQPOINTER_H

//
// main.cpp
//
#include <QCoreApplication>
#include "myqpointer.h"

int main(int argc, char *argv[])
{
    
    

    QCoreApplication a(argc, argv);

     MyQPointer* myQPointer = new MyQPointer();

    delete myQPointer;
//    myQPointer = nullptr;

    if (myQPointer) {
    
    
        qDebug() << "\nMyObject is valid";
        myQPointer->doSomething();
    } else {
    
    
        qDebug() << "\nMyObject is null";
    }

    return a.exec();
}

Running result:
insert image description here
delete pLabel; if you don’t empty it manually later, the output here will be “MyObject is valid”.

Use the smart pointer QPointer, change mian.cc:

//
// main.cpp
//
#include <QCoreApplication>
#include "myqpointer.h"

int main(int argc, char *argv[])
{
    
    
    QCoreApplication a(argc, argv);
    QPointer<MyQPointer> myQPointer(new MyQPointer);
    delete myQPointer;
//    myQPointer = nullptr;

    if (myQPointer) {
    
    
        qDebug() << "\nMyObject is valid";
        myQPointer->doSomething();
    } else {
    
    
        qDebug() << "\nMyObject is null";
    }

    return a.exec();
}

operation result:

insert image description here
The writing method is very simple, that is, directly

 MyQPointer* myQPointer = new MyQPointer();

replace with

QPointer<MyQPointer> myQPointer(new MyQPointer);

At the end of the main function, we call the deleteLater() function of the MyObject object to mark it as pending deletion. Then we check again if the QPointer object is null, at which point it should return a null pointer

♦ Scenario 2:

Modify the above main.cc as follows:

#include <QCoreApplication>
#include "myqpointer.h"


int main(int argc, char *argv[])
{
    
    

    QCoreApplication a(argc, argv);

     MyQPointer* myQPointer1 = new MyQPointer();
     MyQPointer* myQPointer2 = myQPointer1;

    delete myQPointer1;
    myQPointer1 = nullptr;

    if (myQPointer1) {
    
    
        qDebug() << "myQPointer1 is valid";
    } else {
    
    
        qDebug() << "myQPointer1 is null";
    }
    if (myQPointer2) {
    
    
        qDebug() << "myQPointer2 is valid";
    } else {
    
    
        qDebug() << "myQPointer2 is null";
    }
    return a.exec();
}

operation result:

insert image description here

In the above case, myQPointer2 directly copies myQPointer1 and points to the same address. At this time, after deleting myQPointer1, there is no need to delete myQPointer2, otherwise an error will be reported, but myQPointer2 also needs to be manually blanked, otherwise it will become a dangling wild pointer. In the actual situation, you may often forget to set it to empty, or even delete the pointer twice. For novices, this kind of mistake is often made.

Change the above to:

MyQPointer* myQPointer2 = myQPointer1;

change into:

QPointer<MyQPointer> myQPointer2(myQPointer1);
//QPointer<MyQPointer> myQPointer2 = myQPointer1 ;  //两者都行

operation result:

insert image description here

Of course, the best way is that both myQPointer1 and myQPointer2 use smart pointers, so that there is no need to manually empty them. This is the most common usage scenario for QPointer.

♦ Reference:

Smart pointers in Qt

Qt Smart Pointer – QPointer

Guess you like

Origin blog.csdn.net/qq_16504163/article/details/130513681