Puntero inteligente de C++ (diseño y uso)

Hace algún tiempo, aprendí el proyecto Anarchy Engine. Muchas de las capas inferiores solo usan punteros.

Un puntero inteligente es una clase que almacena punteros a objetos asignados dinámicamente (montón), se utiliza para el control de por vida y puede garantizar que los objetos asignados dinámicamente se destruyan automática y correctamente para evitar pérdidas de memoria. Una técnica de implementación común es utilizar el recuento de referencias. Las clases de puntero inteligente asocian un contador con el objeto al que apunta la clase, y el recuento de referencias realiza un seguimiento de cuántos objetos de esa clase comparten el mismo puntero. Cada vez que se crea un nuevo objeto de la clase, inicializa el puntero y establece el recuento de referencias en 1; cuando el objeto se crea como una copia de otro objeto, el constructor de la copia copia el puntero y aumenta el recuento de referencias correspondiente; asigna un valor a un objeto, el operador de asignación disminuye el recuento de referencia del objeto al que apunta el operando izquierdo (si el recuento de referencia se reduce a 0, el objeto se elimina) y aumenta el recuento de referencia del objeto al que apunta el operando derecho ; cuando se llama al destructor, el constructor disminuye el recuento de referencias (elimina el objeto subyacente si el recuento de referencias cae a 0).
    Un puntero inteligente es una clase que simula acciones de puntero. Todos los punteros inteligentes sobrecargan los operadores -> y *. Hay muchas otras funciones de los punteros inteligentes, la más útil es la destrucción automática. Esto es principalmente para utilizar el alcance limitado de los objetos de la pila y el destructor de objetos temporales (implementación de alcance limitado) para liberar memoria. Por supuesto, los punteros inteligentes son más que eso, incluida la capacidad de modificar el objeto de origen al copiar. Los punteros inteligentes tienen diferentes diseños según los diferentes requisitos (copia en escritura, asignación significa liberar la propiedad del objeto, recuento de referencias, etc., transferencia de control, etc.). auto_ptr es un puntero inteligente común.

Los punteros inteligentes generalmente se implementan con plantillas de clase:
plantilla <clase T>
clase smartpointer
{ privado:  T *_ptr; público:  smartpointer(T *p) : _ptr(p) //constructor  {





 }
 T& operador *() // Sobrecarga * operador
 {   return *_ptr;  }  T* operador ->() // Sobrecarga -> operador  {   return _ptr;  }  ~smartpointer() // Destructor  {   eliminar _ptr;  } };










 

Existen dos estrategias clásicas para implementar el recuento de referencias, una de las cuales se utilizará aquí. En el método utilizado aquí, se debe definir una clase concreta separada para encapsular el recuento de referencias y los punteros relacionados:

clase U_Ptr 

    clase amiga HasPtr; 
    int *ip; 
    tamaño_t uso; 
    U_Ptr(int *p): ip(p), use(1) 
    { 
        cout << "¡Se llamó al constructor U_ptr!" << endl; 
    } 
    ~U_Ptr() 
    { 
        eliminar ip; 
        cout << "¡Se llamó al distructor U_ptr!" << endl; 
    } 
}; 

La clase HasPtr necesita un destructor para eliminar el puntero. Sin embargo, el destructor no puede eliminar el puntero incondicionalmente. "
      La condición es el recuento de referencias.
Si el objeto es apuntado por dos punteros, eliminar uno de los punteros no llamará al destructor del puntero, porque hay otro puntero apuntando al objeto en este momento. Parece que el puntero inteligente es principalmente para evitar un comportamiento de destrucción inadecuado y evitar punteros colgantes.

     Como se muestra en la figura anterior, HasPtr es un puntero inteligente y U_Ptr es un contador; tiene un uso variable y una IP de puntero, y el uso registra cuántos objetos HasPtr apuntan. al objeto *ip. Suponiendo que dos objetos HasPtr p1 y p2 apuntan a U_Ptr ahora, ahora elimino p1, la variable de uso se reducirá en 1, U_Ptr no se destruirá, entonces el objeto señalado por U_Ptr no será destruido, entonces p2 todavía apunta a El objeto original no se convertirá en un puntero colgante. Cuando se elimina p2, la variable de uso se reducirá de 1 a 0. En este momento, el objeto U_Ptr se destruirá y el objeto señalado por U_Ptr También se destruirá. Habrá pérdidas de memoria. 
    Las clases que contienen punteros necesitan especial atención al control de copia , porque al copiar un puntero, solo se copia la dirección en el puntero, no el objeto señalado por el puntero. La mayoría de las
    clases de C++ administran el puntero miembros de una de tres maneras
    (1) Independientemente de los miembros del puntero. Al copiar, solo se copia el puntero y el objeto señalado por el puntero no se copia. Cuando uno de los punteros libera el espacio del objeto al que apunta, los otros punteros se convierten en punteros flotantes. Este es un extremo (2)
    cuando Al copiar, el puntero se copia y el objeto señalado por el puntero también se copia. Esto puede causar una pérdida de espacio. Debido a que la copia del objeto señalado por el puntero no es necesariamente necesario (3
   )El tercero es un método de compromiso. Utilice una clase auxiliar para gestionar la copia de punteros. Hay un puntero en la clase original que apunta a la clase auxiliar, y los miembros de datos de la clase auxiliar son un contador y un puntero (que apunta al original) (esta vez es la implementación del puntero inteligente).
     De hecho, el recuento de referencias de los punteros inteligentes es similar al mecanismo de recolección de basura de Java : el juicio de basura de Java es muy simple: si un objeto no hace referencia a él, entonces el objeto es basura. El sistema está listo para el reciclaje.
     La declaración del puntero inteligente HasPtr es la siguiente. Guarda un puntero al objeto U_Ptr . El objeto U_Ptr apunta al objeto base int real. El código es el siguiente:

//
/// autor: Jeson Yang
/// fecha: 2014.9.17
//

 

#include<iostream> 
usando el espacio de nombres std; 

// Definir la clase U_Ptr utilizada solo por la clase HasPtr para encapsular el recuento de uso y los punteros relacionados 
// Todos los miembros de esta clase son privados, no queremos que los usuarios normales utilicen la clase U_Ptr, por lo que no tiene ninguna miembros públicos 
// Will HasPtr La clase se establece como amiga para que sus miembros puedan acceder a los miembros de U_Ptr 
clase U_Ptr 

 clase amiga HasPtr; 
 int *ip; 
 size_t use; 
 U_Ptr(int *p): ip(p), use (1) 
 { 
  cout << "Constructor U_ptr llamado!" << endl; 
 } 
 ~U_Ptr() 
 { 
  eliminar ip; 
  cout << "Constructor U_ptr llamado!" << endl; 
 } 
}; 

class HasPtr 

public: 
 // Constructor: p es un puntero a un objeto int creado dinámicamente 
 HasPtr(int *p, int i) : ptr(new U_Ptr(p)) , val(i) 
 { 
  cout << "Constructor HasPtr llamado ! " << "usar = " << ptr->usar << endl; 
 } 

 // Copiar constructor: copiar miembros y agregar 1 al recuento de uso 
 HasPtr(const HasPtr& orig): ptr(orig.ptr), val(orig.val) 
 { 
  ++ptr->use; 
  cout << "Constructor de copia HasPtr llamado ! " << "usar = " << ptr->usar << endl; 
 } 

 // operador de asignación 
 HasPtr& operator=(const HasPtr&); 

 // Destructor: si el recuento es 0, elimina el objeto U_Ptr 
 ~HasPtr() 
 { 
  cout << "Se llamó al constructor HasPtr ! " << "use = " << ptr->use << endl; 
  if (--ptr - >usar == 0) 
   eliminar ptr; 
 } 

 // Obtener miembro de datos 
 int *get_ptr() const 
 { 
  return ptr->ip; 
 } 
 int get_int() const 
 { 
  return val; 
 } 

 // Modificar miembro de datos 
 void set_ptr(int *p) const 
 { 
  ptr->ip = p; 
 } 
 void set_int(int i) 
 { 
  val = i; 
 } 

 // Devuelve o modifica el objeto int subyacente 
 int get_ptr_val() const 
 { 
  return *ptr->ip; 
 } 
 void set_ptr_val(int i) 
 { 
  *ptr->ip = i; 
 } 
private: 
 U_Ptr *ptr; // apunta al use count class U_Ptr 
 int val; 
}; 
HasPtr& HasPtr::operator = (const HasPtr &rhs) // Tenga en cuenta que el operador de asignación agrega 1 a rhs antes de reducir el recuento de uso del operando, evitando así la autoasignación { // 
aumentar 
 The use el recuento en el operando derecho 
 ++rhs.ptr->use; 
 //Disminuya el recuento de uso del objeto del operando izquierdo en 1, si el recuento de uso del objeto se reduce a 0, elimine el objeto 
 si (--ptr- > usar == 0) 
  eliminar ptr; 
 ptr = rhs.ptr; // copiar puntero U_Ptr 
 val = rhs.val; // copiar miembro int 
 return *this; 

int main(void) 

 int *pi = new int(42); 
 HasPtr *hpa = new HasPtr(pi, 100); // constructor 
 HasPtr *hpb = new HasPtr(*hpa); // copiar constructor 
 HasPtr * hpc = new HasPtr(*hpb); // copiar constructor 
 HasPtr hpd = *hpa; // copiar constructor 

 cout << hpa->get_ptr_val() << " " << hpb->get_ptr_val() << endl; 
 hpc->set_ptr_val(10000); 
 cout << hpa->get_ptr_val() << " " << hpb->get_ptr_val() << endl; 
 hpd.set_ptr_val(10); 
 cout << hpa->get_ptr_val() << " " << hpb->get_ptr_val() << endl; 
 eliminar hpa; 
 eliminar hpb; 
 eliminar hpc; 
 cout << hpd.get_ptr_val() << endl; 
 devolver 0; 

El operador de asignación aquí es bastante problemático, permítanme analizarlo con un diagrama:
supongamos que ahora hay dos punteros inteligentes p1 y p2, uno apunta a la memoria con un contenido de 42 y el otro apunta a la memoria con un contenido de 100. , como se muestra en la siguiente figura:

Ahora, I Para realizar la operación de asignación, p2 = p1. comparado con lo anterior

HasPtr& operador=( const  HasPtr&);    // operador de asignación  

En este punto, rhs es p1, primero agregue 1 al uso de ptr señalado por p1,

++rhs.ptr->use;      // incrementa el recuento de uso en el operando derecho  

Entonces hazlo:

si (--ptr->use == 0) 
        elimina ptr; 

debido a que el objeto señalado por p2 ya no es apuntado por p2, entonces el objeto tiene un puntero menos al que apuntar, por lo que el uso se reduce en 1;
en este momento , la condición establecida. Porque el uso de u2 es 1. Luego, ejecute el destructor de U_Ptr, y en el destructor de U_Ptr, se realiza la operación de eliminación de IP, por lo que se libera la memoria y no habrá problemas de pérdida de memoria.
Las siguientes operaciones son naturales y no hace falta decirlo:

ptr = rhs.ptr; // copia el puntero U_Ptr 
    val = rhs.val; // copia el miembro int 
    return *this; 

 

Después de completar la operación de asignación, queda como se muestra en la siguiente figura. La parte marcada en rojo es la parte cambiada:

y también cabe señalar que al sobrecargar el operador de asignación, se debe prestar atención a comprobar la situación de la autoasignación.
Como se muestra en la figura:


En este momento, realice la operación de p1 = p1. Entonces, primero u1.use aumenta de 1 a 2; luego, u1.use disminuye de 1 a 1. Entonces no se realizará la operación de eliminación y el resto de las operaciones podrán realizarse sin problemas. Según "C++ Primer", "este operador de asignación aumenta el recuento de uso de rhs en 1 antes de reducir el recuento de uso del operando izquierdo, evitando así la autoasignación". Oye, en fin, así lo entiendo yo. Por supuesto, en la función del operador de asignación, se puede hacer como de costumbre:

si (esto == &rhs) 
        devuelve *esto;

Supongo que te gusta

Origin blog.csdn.net/yc7369/article/details/39351783
Recomendado
Clasificación