El unique_ptr del puntero inteligente (explicación detallada)

Unique_ptr de puntero inteligente

Creación e inicialización de punteros

Ejemplo de código:

#include <iostream>  
#include <memory>  
using namespace std;  
  
int main()  
{  
    int* ptr2 = new int(11);  
    unique_ptr<int> ptr = make_unique<int>(10), ptr1(ptr2);  
    cout << *ptr1 << endl;  
    cout << *ptr2 << endl;  
} 

 

resultado de la operación:

 

Vemos que hay dos formas de inicializar el puntero unique_ptr:

① Llame a make_unique <type> (valor) para asignar un valor en la definición;

② Como shared_ptr y auto_ptr, el puntero devuelto al llamar a new / new [] se puede usar para inicializar el puntero unique_ptr;

#include <iostream>  
#include <memory>  
using namespace std;  
  
int main()  
{  
    unique_ptr<int[]> ptr(new int[2]{ 1,2 });  
    cout << ptr[0] << endl; // 输出1  
    cout << ptr[1] << endl; // 输出2  
}  

 

Sin embargo, el siguiente método de inicialización es incorrecto:

① No puede usar unique_ptr, shared_ptr, auto_ptr y débil_ptr para inicializar / asignar el puntero unique_ptr;

Ejemplo de código:

#include <iostream>  
#include <memory>  
using namespace std;  
  
int main()  
{  
    shared_ptr<int> ptr = make_shared<int>(10);  
    weak_ptr<int> ptr1(ptr);  
    auto_ptr<int> ptr2(new int(11));  
    unique_ptr<int> ptr3(ptr), ptr4(ptr1), ptr5(ptr2);  // 错误的初始化方式
} 

 

razón equivocada:

 

② Trate de no permitir que el puntero unique_ptr mantenga el puntero que se ha inicializado con el nuevo valor de retorno;

Ejemplo de código:

#include <iostream>  
#include <memory>  
using namespace std;  
  
int main()  
{  
    int* ptr = new int(10);  
    unique_ptr<int> ptr1(ptr); // 可以,但最好不要这样  
    unique_ptr<int> ptr2(new int(11)); // 这样最好  
}  

 

Los beneficios de esto:

unique_ptr enfatiza la unicidad en todo el alcance, lo que significa "en su alcance, hay uno y solo un puntero para mantener este espacio de memoria de pila", si usamos el nuevo valor de retorno inicializado int * type puntero Para inicializar unique_ptr, las siguientes situaciones puede ocurrir:

#include <iostream>  
#include <memory>  
using namespace std;  
  
int main()  
{  
    int* ptr = new int(10);  
    unique_ptr<int> ptr1(ptr); // 使用ptr初始化unique_ptr指针  
    shared_ptr<int> ptr2(ptr); // 又使用ptr初始化shared_ptr指针  
}  

 

Mensaje de error:

(Mensaje de error de liberación repetida de eliminación de memoria)

 

La razón de esto es que después de inicializar el puntero unique_ptr al puntero ptr, el resultado es que el puntero unique_ptr y el puntero ptr mantienen conjuntamente el espacio de memoria del montón al que apunta ptr. En este momento, se descubrirá un problema importante. Después de que ptr se asigna al puntero unique_ptr, también se pueden inicializar otros tipos de punteros ", lo que viola seriamente el puntero" unique_ptr en su alcance ". Espero que esta situación no suceda tanto como sea posible, de lo contrario, hará que la memoria libere repetidamente el programa y provoque un bloqueo debido al uso inadvertido de ptr nuevamente.

Precauciones al asignar unique_ptr

① Al pasar el parámetro unique_ptr a la función, debe usar "pasar por referencia"

⑴ Ejemplos de código correcto:

#include <iostream>  
#include <memory>  
using namespace std;  
  
void ShowInf(unique_ptr<int>& obj)  // 引用传参
{  
    cout << *obj << endl;  
}  
  
int main()  
{  
    unique_ptr<int> ptr = make_unique<int>(10);  
    ShowInf(ptr); // 输出10  
}  

 

⑵ ¿Por qué no puede utilizar la transferencia de valor para transferir parámetros de función?

Sabemos que "unique_ptr prohíbe todos los comportamientos que quieran compartir la propiedad de la memoria con él". ¿No significa la transferencia de valor copiar la dirección apuntada por el puntero a una "variable de puntero temporal cuyo alcance está solo dentro de la función"? ya que es una copia, significa que hay alguien que quiera dividir la propiedad de esta área de memoria por unique_ptr, ¡este es un acto estrictamente prohibido!

② Utilice el puntero de área de montón devuelto por new para inicializar el puntero de unique_ptr durante la definición, pero una vez completada la definición, el puntero de área de pila devuelto por new no se puede asignar al puntero de unique_ptr.

Ejemplos de códigos de error:

#include <iostream>  
#include <memory>  
using namespace std;  
  
int main()  
{  
    int* ptr1 = new int(11);  
    unique_ptr<int> ptr(nullptr);  
    //ptr = ptr1; // 错误代码  
}  

 

Mensaje de error:

 

Código correcto:

#include <iostream>  
#include <memory>  
using namespace std;  
  
int main()  
{  
    int* ptr1 = new int(11);  
    unique_ptr<int> ptr(nullptr);  
    ptr = make_unique<int>(10);  
}  

 

La única opción para asignar un valor a unique_ptr: use make_unique <type> (valor). De hecho, la capa inferior de make_unique también usa new para aplicar dinámicamente al espacio de memoria y devolver un puntero de tipo unique_ptr.

③ Debido a las características únicas de unique_ptr, es decir, solo puede apuntar a un área de memoria durante su ciclo de vida, por lo que los punteros inteligentes de tipo unique_ptr no se pueden copiar. Como unique_ptr no se puede copiar, solo se puede mover. Por lo tanto, no podemos crear una copia del objeto unique_ptr mediante el constructor de copia o el operador de asignación. Cuando utilice cualquier tipo de constructor de copia unique_ptr o operador de asignación sobrecargado, el compilador le dirá "la función miembro correspondiente ha sido eliminada, no la vuelva a llamar".

// 编译错误 : unique_ptr 不能复制  
std::unique_ptr<Task> taskPtr3 = taskPtr2; // Compile error  
  
// 编译错误 : unique_ptr 不能复制  
taskPtr = taskPtr2; //compile error  

 

Análisis de la función de miembro unique_ptr

① función get: devuelve la dirección del puntero mantenida por unique_ptr

⑴ Función:

Devuelve la dirección del puntero mantenida por unique_ptr.

⑵ Ejemplo de código:

#include <iostream>  
#include <memory>  
using namespace std;  
  
int main()  
{  
    int* ptr1 = new int(11);  
    unique_ptr<int> ptr(nullptr);  
    ptr = make_unique<int>(10);  
  
    cout << *ptr.get() << endl; // 返回10  
}  

 

② función de liberación: libera la propiedad de su puntero original asociado y devuelve el puntero original

⑴ Función:

Libere la propiedad de su puntero original asociado y devuelva el puntero original.

⑵ Ejemplo de función:

#include <iostream>  
#include <memory>  
using namespace std;  
  
int main()  
{  
    unique_ptr<int> ptr = make_unique<int>(10);  
    int* ptr1 = ptr.release();  // 返回与unique_ptr类型相同类型的指针
    if (ptr == nullptr)  
    {  
        cout << "unique_ptr类型的指针ptr已被置空,释放了内存的控制权" << endl;  
    }  
    if (ptr1 != nullptr)  
    {  
        cout << "原本unique_ptr指向的内存空间并没有被释放掉" << endl;  
    }  
}  

 

Resultado de salida:

 

③ función de reinicio: libere la memoria puntiaguda y libere la propiedad de esta memoria

⑴ Función:

Libere la memoria puntiaguda y libere la propiedad de esta memoria.

⑵ Ejemplo de código:

#include <iostream>  
#include <memory>  
using namespace std;  
  
int main()  
{  
    unique_ptr<int> ptr = make_unique<int>(10);  
    cout << "ptr复位前:" << *ptr << endl;  
    ptr.reset(new int(100)); // 使得ptr指向一块新的内存空间,同时释放掉原来指向的内存空间  
    cout << "ptr复位后:" << *ptr << endl;  
    ptr.reset(nullptr); //与ptr.reset()效果等价,都是使得ptr指向nullptr  
    cout << "ptr是否被置空:" << (ptr == nullptr) << endl;  
} 

​​​​​​​ 

resultado de la operación:

 

⑶ Análisis de resultados:

De hecho, la función de miembro de reinicio aquí es la misma que la función de miembro de reinicio de shared_ptr en que cambia el espacio de memoria al que apunta el puntero, pero la diferencia es: porque solo un puntero de unique_ptr apunta a esta área de memoria abierta en el montón área, cuando Cuando el puntero apunte a otro espacio de memoria, se liberará el espacio de memoria original. Por lo tanto, a menudo llamo "la función que posee la función de reinicio del puntero unique_ptr" como "la función de destrucción del puntero, es decir, la función legendaria de autodestrucción".

 

④ función de intercambio: intercambia dos punteros unique_ptr al área (dirección de intercambio)

⑴ Función función:

Intercambie dos punteros unique_ptr al área (dirección de intercambio)

⑵ Ejemplo de código:

#include <iostream>  
#include <memory>  
using namespace std;  
  
int main()  
{  
    unique_ptr<int> ptr = make_unique<int>(10), ptr1(nullptr);  
    cout << "交换前:" << endl;  
    cout << "ptr:" << ptr.get() << endl;  
    cout << "ptr1:" << ptr1.get() << endl;  
    ptr.swap(ptr1);  
    cout << "交换后:" << endl;  
    cout << "ptr:" << ptr.get() << endl;  
    cout << "ptr1:" << ptr1.get() << endl;  
}  

 

resultado de la operación:

 

El resultado muestra que las direcciones apuntadas por los dos punteros unique_ptr <int> de ptr y ptr1 han sido intercambiadas.

⑤ función get_deleter: devuelve un eliminador personalizado

⑴ Función:

Sabemos que cuando un puntero inteligente termina o libera el espacio de memoria apuntado (se ejecuta la función de miembro unique_ptr :: reset ()), se llamará al eliminador para eliminar el espacio de memoria del montón al que apunta el puntero. Si se realiza esta operación mediante el control del sistema, solo podemos saber si la memoria se ha liberado de manera efectiva o si queremos controlar el "poder de liberación de la memoria" en nuestras propias manos (por ejemplo: podemos contar cuántas veces se ha liberado el sistema mediante el puntero unique_ptr Memoria ... y otras operaciones más fáciles de usar), esto nos ayuda a contar cierta información de liberación de memoria y optimizar el código en función de esta información para optimizar el código y maximizar la eficiencia operativa del código.

⑵ Ejemplo de código:

El siguiente código muestra "cuántas veces el sistema liberó memoria a través del puntero ptr":

#include <iostream>  
#include <memory>  
using namespace std;  
  
class deleter  
{  
private:  
    int count;  
public:  
    deleter() :count(0) {};  
    void ShowInf()  
    {  
        cout << "调用此删除器的次数为" << this->count << endl;  
    }  
    template <typename T>  
    void operator()(T* ptr)  
    {  
        this->count++;  
        cout << "调用删除器的次数为" << this->count << endl;  
        delete ptr;  
    }  
};  
  
int main()  
{  
    unique_ptr<int, deleter> object_a(new int(1));  
    object_a.reset(new int(2));  
    object_a.get_deleter().ShowInf();  
}  

 

resultado de la operación:

 

La primera vez fue porque llamamos a la función de miembro reset para cambiar el espacio de memoria apuntado por ptr y liberamos el área de memoria apuntada originalmente por ptr; la segunda vez llamamos al objeto anónimo de la clase de borrador devuelto por get_deleter () sin parámetros función miembro (objeto temporal) para llamar a la función miembro compartida de la clase (la interfaz externa de la clase) para mostrar "Hasta ahora, cuántas veces el sistema ha liberado la memoria del montón a través del puntero ptr"; la tercera vez es porque es necesario pasar el ciclo de vida del puntero ptr Llame al borrador de ptr para liberar el espacio de memoria señalado por ptr Aquí hemos personalizado el borrador para mantener firmemente el poder de borrar la memoria apuntada por ptr en nuestras propias manos. Cuando ptr libera la memoria, llamaremos a nuestro functor delete ().

Además, también podemos usar el mismo objeto de eliminación en varios punteros inteligentes de tipo unique_ptr, para contar el número total de veces que varios o todos los punteros inteligentes de tipo unique_ptr eliminan el área de memoria durante la ejecución del código. El ejemplo de código es el siguiente:

#include <iostream>  
#include <memory>  
using namespace std;  
  
template <typename T>  
class deleter  
{  
private:  
    int count;  
public:  
    deleter() :count(0) {};  
    void ShowInf()  
    {  
        cout << "调用此删除器的次数为" << this->count << endl;  
    }  
    void operator()(T* ptr)  
    {  
        this->count++;  
        cout << "调用删除器的次数为" << this->count << endl;  
        delete ptr;  
    }  
};  
  
int main()  
{  
    unique_ptr<int, deleter<int> > object_a(new int(1));  
    unique_ptr<int, deleter<int>& > object_b(new int(3), object_a.get_deleter());  
}  

 

Los resultados son los siguientes:

 

Podemos ver que object_a y object_b comparten un eliminador, por lo que podemos contar el número de veces que los dos punteros unique_ptr de object_a y object_b liberan memoria. Podemos generalizar esto para: "Contar el número de veces que el puntero de tipo unique_ptr libera memoria en todo el programa".

También puede usar el siguiente formato de código para contar la cantidad de veces que el sistema libera memoria a través de los punteros object_a y object_b:

#include <iostream>  
#include <memory>  
using namespace std;  
  
template <typename T>  
class deleter  
{  
private:  
    int count;  
public:  
    deleter() :count(0) {};  
    void ShowInf()  
    {  
        cout << "调用此删除器的次数为" << this->count << endl;  
    }  
    void operator()(T* ptr)  
    {  
        this->count++;  
        cout << "调用删除器的次数为" << this->count << endl;  
        delete ptr;  
    }  
};  
  
int main()  
{  
    deleter<int> del;  
    unique_ptr<int, deleter<int>& > object_a(new int(1), del);  // 传入del这个deleter类型的对象本身
    unique_ptr<int, deleter<int>& > object_b(new int(3), del);  // 传入del这个deleter类型的对象本身
}  

 

¿Cómo transferir la propiedad del objeto unique_ptr?

Ejemplo de código:

#include <iostream>  
#include <memory>  
using namespace std;  
  
int main()  
{  
    unique_ptr<int> ptr(new int(10));  
    cout << "调用移动语义前:" << endl;  
    cout << "ptr:" << ptr.get() << endl;  
    unique_ptr<int> ptr1 = move<unique_ptr<int>& >(ptr);  
    cout << "ptr指向是否为空:" << (ptr == nullptr) << endl;  
    cout << "ptr1指向的元素为" << *ptr1 << endl;  
    cout << "使用移动语义后:" << endl;  
    cout << "ptr:" << ptr.get() << endl;  
    cout << "ptr1:" << ptr1.get() << endl;  
}  

 

resultado de la operación:

 

Podemos ver en los resultados: el resultado de la semántica de movimiento es transferir la dirección almacenada en un unique_ptr a otro puntero de unique_ptr y anular el puntero del tipo de fuente unique_ptr (= nullptr). Esto es más seguro, pero debemos verificar si los siguientes punteros no están vacíos antes de usar punteros por si acaso.

Supongo que te gusta

Origin blog.csdn.net/weixin_45590473/article/details/113101912
Recomendado
Clasificación