Introducción a los punteros inteligentes Qt: QSharedPointer, QWeakPointer, QScopedPointer, QPointer (con ejemplos)

1. Información general

En el caso de la asignación de memoria dinámica, es necesario asegurarse de que la propiedad de los objetos se gestione y transfiera correctamente. El uso de punteros inteligentes puede ayudarnos a administrar automáticamente el ciclo de vida y la propiedad de los objetos, evitando pérdidas de memoria y punteros colgantes.

♦ ¿Cuándo necesita usar punteros inteligentes?

  • En el caso de utilizar objetos QObject, es necesario asegurarse de que los ciclos de vida de los objetos y las relaciones padre-hijo se gestionen correctamente. El ciclo de vida de los objetos QObject se ve afectado por la relación padre-hijo, por lo que se deben usar punteros inteligentes como QPointer para administrar los punteros de los objetos QObject.

  • En la programación de subprocesos múltiples, es necesario asegurarse de que no se produzcan condiciones de carrera cuando varios subprocesos acceden a objetos compartidos. El uso de punteros inteligentes puede evitar condiciones de carrera porque cuenta automáticamente las referencias a los objetos, lo que garantiza que los objetos no se eliminen hasta que se liberen todas las referencias.

  • Cuando utilice el manejo de excepciones, debe asegurarse de que todos los objetos asignados dinámicamente se eliminen correctamente cuando la función regrese. El uso de punteros inteligentes garantiza que todos los objetos asignados dinámicamente se eliminen correctamente cuando la función regresa para evitar pérdidas de memoria.

  • Evite olvidarse de llamar eliminar

En resumen, cuando necesitamos asignar memoria dinámicamente y garantizar la correcta gestión del ciclo de vida del objeto, la propiedad, la seguridad de subprocesos, etc., podemos considerar el uso de punteros inteligentes.

2. ¿Cuántos punteros inteligentes hay en Qt?

Los de uso común incluyen QSharedPointer, QWeakPointer, QScopedPointer y QPointer;

  • QSharedPointer para casos de propiedad compartida
  • QWeakPointer es para cuando no comparte la propiedad pero necesita acceder a objetos administrados por QSharedPointer
  • QScopedPointer es adecuado para casos de propiedad exclusiva.
  • QPointer se usa principalmente para resolver el problema de los punteros colgantes y es adecuado para referencias entre objetos Qt

    Los detalles son los siguientes:

2.1 Instancia QSharedPointer

QSharedPointer: una referencia fuerte, aproximadamente equivalente a shared_ptr en el estándar C++11, que se usa para administrar la propiedad compartida de objetos asignados dinámicamente, es decir, varios objetos QSharedPointer pueden apuntar al mismo objeto y compartir la administración de memoria del objeto.

Utiliza el conteo de referencias para rastrear el uso de objetos, y cuando se destruye el último objeto QSharedPointer, liberará automáticamente la memoria del objeto. Debido al uso del conteo de referencias, QSharedPointer puede manejar automáticamente el ciclo de vida del puntero, evitando problemas como pérdidas de memoria y punteros colgantes, por lo que es el puntero inteligente más utilizado en 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;
}

Resultado de salida:
inserte la descripción de la imagen aquí

Cabe señalar que QSharedPointer solo puede administrar la memoria de los objetos asignados dinámicamente. Si lo usamos para apuntar a un objeto de pila o un objeto global, entonces no liberará automáticamente la memoria del objeto, lo que puede provocar un bloqueo del programa o una pérdida de memoria.

Comprensión profunda: análisis del código fuente del puntero inteligente C++ (shared_ptr/weak_ptr)

2.2 Instancias QSharedPointer y QWeakPointer

QWeakPointer: referencia débil, aproximadamente equivalente a débil_ptr en el estándar C++ 11, utilizada para acceder al objeto señalado por QSharedPointer sin compartir la propiedad. QWeakPointer apunta al objeto administrado por QSharedPointer, pero no aumentará el recuento de referencias del objeto ni afectará el ciclo de vida del objeto. Cuando se libera el objeto, QWeakPointer se establecerá automáticamente en un puntero nulo, evitando el problema de los punteros colgantes.

QWeakPointer es un puntero inteligente introducido para cooperar con QSharedPointer. Es más como un asistente de QSharedPointer, que observa el uso de recursos como un espectador.

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

Imprimir resultado:

inserte la descripción de la imagen aquí
En el ejemplo, se crean un objeto QSharedPointer y un objeto QWeakPointer y ambos apuntan a un objeto MyClass. Al final de la función principal, use shared.clear() para liberar la propiedad del objeto MyClass. En este momento, el recuento de referencias del objeto MyClass es 0 y se eliminará automáticamente, y en este momento el objeto QWeakPointer débil también es nulo.

En uso real, se puede volver a empaquetar:

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

2.3 Instancia QScopedPointer

QScopedPointer es similar a unique_ptr en C++11, que se usa para administrar la propiedad exclusiva de los objetos asignados dinámicamente, es decir, solo un QScopedPointer puede apuntar al objeto a la vez.

QScopedPointer utiliza tecnología RAII (la adquisición de recursos es inicialización) Cuando se destruye QScopedPointer, automáticamente liberará la memoria del objeto administrado. QScopedPointer no admite operaciones de copia y asignación, esto es para evitar el problema de compartir la propiedad entre varios punteros. Si necesita transferir la propiedad entre varios punteros, debe usar QSharedPointer o QWeakPointer.

Aquí hay un ejemplo simple:

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

El resultado es el siguiente:

inserte la descripción de la imagen aquí

2.4 Instancias de QPointer

Dado que el ciclo de vida de los objetos QObject se ve afectado por la relación padre-hijo, cuando se elimina el objeto principal, también se eliminarán todos los objetos secundarios. El uso de QPointer puede evitar el problema de que cuando se elimina el objeto principal, el puntero del objeto secundario apunta al objeto que se eliminó.

Por lo general, cuando eliminamos un puntero manualmente, debemos vaciarlo nuevamente; de ​​lo contrario, se convertirá en un puntero salvaje colgante, luego QPointer ayudará con esto, y se configurará automáticamente en NULL

♦ Escenario 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();
}

Resultado de la ejecución:
inserte la descripción de la imagen aquí
elimine pLabel; si no lo vacía manualmente más tarde, el resultado aquí será "MyObject is valid".

Use el puntero inteligente QPointer, cambie 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();
}

resultado de la operación:

inserte la descripción de la imagen aquí
El método de escritura es muy simple, es decir, directamente

 MyQPointer* myQPointer = new MyQPointer();

reemplazar con

QPointer<MyQPointer> myQPointer(new MyQPointer);

Al final de la función principal, llamamos a la función deleteLater() del objeto MyObject para marcarlo como pendiente de eliminación. Luego, verificamos nuevamente si el objeto QPointer es nulo, momento en el que debería devolver un puntero nulo

♦ Escenario 2:

Modifique el main.cc anterior de la siguiente manera:

#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();
}

resultado de la operación:

inserte la descripción de la imagen aquí

En el caso anterior, myQPointer2 copia directamente myQPointer1 y apunta a la misma dirección. En este momento, después de eliminar myQPointer1, no es necesario eliminar myQPointer2, de lo contrario se informará un error, pero myQPointer2 también debe borrarse manualmente, de lo contrario se convertirá en un puntero salvaje colgante. En la situación real, a menudo puede olvidar configurarlo para que esté vacío, o incluso eliminar el puntero dos veces. Para los principiantes, este tipo de error se comete a menudo.

Cambie lo anterior a:

MyQPointer* myQPointer2 = myQPointer1;

cambie a:

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

resultado de la operación:

inserte la descripción de la imagen aquí

Por supuesto, la mejor manera es que tanto myQPointer1 como myQPointer2 usen punteros inteligentes, por lo que no es necesario vaciarlos manualmente. Este es el escenario de uso más común para QPointer.

♦ Referencia:

Punteros inteligentes en Qt

Puntero inteligente Qt – QPointer

Supongo que te gusta

Origin blog.csdn.net/qq_16504163/article/details/130513681
Recomendado
Clasificación