Comprender los punteros inteligentes en C++

Mohd Mohtashim Nawaz 12 octubre 2023
  1. Implementar un puntero inteligente en C++
  2. Utilice Smart Pointer Destructor para evitar fugas de memoria en C++
  3. Tipos de punteros inteligentes en C++
  4. Cuándo usar punteros inteligentes en C++
Comprender los punteros inteligentes en C++

Este artículo discutirá los punteros inteligentes en C++, cómo evitan pérdidas de memoria, los tipos de punteros inteligentes y los casos en los que debemos usarlos.

Implementar un puntero inteligente en C++

Podemos definir el puntero inteligente en C++ como una plantilla de clase que podemos usar para mantener el puntero sin formato original. Nuestra clase de puntero inteligente contiene una variable de puntero para contener nuestros métodos de anulación de operador, destructor y puntero sin procesar.

Sin embargo, no estamos restringidos a incluir solo estos campos y métodos. Podemos agregar nuestros métodos según sea necesario.

En lugar de manejar directamente el puntero sin procesar, usamos esta clase para manejar el puntero. Y en lugar de definir una clase personalizada para punteros inteligentes en C++, también podemos usar la biblioteca estándar de C++.

Definamos una clase personalizada para la implementación de punteros inteligentes de C++.

#include <iostream>

using namespace std;

template <class T>
class CustomSmartPointer {
  T *data;

 public:
  explicit CustomSmartPointer(T *ptr = NULL)  // Constructor to assign pointer
  {
    data = ptr;
  }
  ~CustomSmartPointer()  // Destructor that deallocated memory
  {
    delete data;
  }
  // We overload * and -> operators
  T *operator->() { return data; }
  T &operator*() { return *data; }
};

int main() {
  CustomSmartPointer<int> myPtr(new int());
  *myPtr = 100;
  cout << *myPtr << endl;
  // After executing above statement, memory allocated to myPtr is deallocated.
  return 0;
}

Producción :

stark@stark:~/eclipse-workspace/smart_pointer$ g++ custom_smart_prt.cc
stark@stark:~/eclipse-workspace/smart_pointer$ ./a.out
100

Utilice Smart Pointer Destructor para evitar fugas de memoria en C++

La presencia de un destructor hace que nuestra clase de puntero inteligente sea especial y la distingue de los punteros sin formato. Usamos el destructor de puntero inteligente de C++ para desasignar la memoria asignada a nuestro puntero.

Se llama automáticamente a un destructor y desasigna la memoria asignada cuando un objeto de clase queda fuera del alcance. Si hemos automatizado el proceso de desasignación de memoria, no debemos preocuparnos por las fugas de recursos causadas por los punteros que podríamos olvidar desasignar.

El destructor de puntero inteligente actúa como un recolector de basura automático en C++, similar a Java y C#.

Veamos un ejemplo de una fuga de recursos causada por punteros sin procesar.

#include <iostream>

using namespace std;

class student {
 private:
  string name;
  int marks;

 public:
  void setMarks(int m) { marks = m; }
  void setName(string n) { name = n; }
  int getMarks() { return marks; }
  string getName() { return name; }
};

int main() {
  while (true) {
    student* p = new student;
    *p->name = "Stark";
    *p->marks = 100;
  }
  return 0;
}

Como podemos ver, estamos inicializando el puntero de tipo alumno en un bucle infinito. No hemos desasignado la memoria del puntero, por lo que esto seguiría asignando los recursos hasta que se ocupe toda la memoria.

No debemos ejecutar este programa por mucho tiempo. De lo contrario, nuestro sistema podría bloquearse debido a un desbordamiento de memoria.

Sin embargo, si usáramos el puntero inteligente, la memoria se habría desasignado automáticamente después de cada bucle. Por lo tanto, a la vez, solo se habría ocupado una memoria de puntero.

Tipos de punteros inteligentes en C++

Tenemos tres tipos diferentes de punteros inteligentes en C++. Estos son los tipos implementados en la biblioteca de C++ para punteros inteligentes.

unique_ptr

Este tipo de puntero inteligente nos permite asignar solo un usuario para el puntero sin procesar subyacente. Eso significa que no podemos asignar el mismo puntero subyacente a dos o más objetos.

Deberíamos usar este tipo de puntero por defecto hasta que podamos compartir la memoria.

shared_ptr

Como sugiere el nombre, podemos asignar múltiples propietarios a un puntero sin formato utilizando este puntero inteligente. Esto utiliza un contador de referencia para realizar un seguimiento del número de asignados.

weak_ptr

Este tipo de puntero inteligente es muy similar al shared_ptr, excepto que no participa en el contador de referencia. No quiere decir que no tenga contador de referencia, pero su referencia no se cuenta.

En cambio, usamos su contador de referencias para contar otras referencias compartidas. Podemos usar este puntero cuando no necesitamos un control estricto sobre la propiedad.

Proporciona acceso a uno o más objetos shared_ptr. Sin embargo, tiene una ventaja ya que elimina la posibilidad de interbloqueo.

Veamos una implementación de puntero inteligente en C++.

#include <iostream>
#include <memory>

using namespace std;

class student {
 private:
  string name;
  int marks;

 public:
  void setMarks(int m) { marks = m; }
  void setName(string n) { name = n; }
  int getMarks() { return marks; }
  string getName() { return name; }
};

int main() {
  unique_ptr<student> ptr1(new student);
  ptr1->setName("Stark");
  ptr1->setMarks(100);
  cout << "unique_ptr output >>\n";
  cout << ptr1->getName() << " : " << ptr1->getMarks() << "\n";

  cout << "shared_ptr output >> \n";
  shared_ptr<student> ptr2(new student);
  ptr2->setName("Tony");
  ptr2->setMarks(99);
  cout << ptr2->getName() << " : " << ptr2->getMarks() << "\n";

  shared_ptr<student> ptr22;
  ptr22 = ptr2;
  cout << ptr22->getName() << " : " << ptr22->getMarks() << "\n";
  cout << "Reference count of shared_ptr: " << ptr2.use_count() << endl;

  auto ptr = make_shared<student>();
  ptr->setName("Jarvis");
  ptr->setMarks(98);

  cout << "Weak pointer output >>" << endl;
  weak_ptr<student> ptr3;
  ptr3 = ptr;
  cout << "Reference count of weak_ptr: " << ptr3.use_count() << endl;
  shared_ptr<student> ref = ptr3.lock();
  cout << ref->getName() << " : " << ref->getMarks() << "\n";

  return 0;
}

Producción :

stark@stark:~/eclipse-workspace/smart_pointer$ g++ types_smart.cc
stark@stark:~/eclipse-workspace/smart_pointer$ ./a.out
unique_ptr output >>
Stark : 100
shared_ptr output >>
Tony : 99
Tony : 99
Reference count of shared_ptr: 2
Weak pointer output >>
Reference count of weak_ptr: 1
Jarvis : 98

Debemos tener en cuenta que la cuenta de referencia del puntero débil es 1 porque no participa en la cuenta de referencia.

Cuándo usar punteros inteligentes en C++

No debemos usar punteros inteligentes para todo. Dado que usamos una plantilla de clase para punteros inteligentes, generalmente tienen un rendimiento degradado y aumentan la complejidad del programa.

Deberíamos usar punteros sin formato para aplicaciones críticas para el rendimiento y programas pequeños como práctica recomendada. Sin embargo, siempre debemos desasignar los recursos del puntero después de que finalice su uso.

Si nuestro código es grande y es difícil desasignar memoria para cada puntero manualmente, deberíamos usar los punteros inteligentes. Deberíamos preferir usar punteros inteligentes si la aplicación no es crítica para el rendimiento.

Si necesitamos manejar excepciones, recuentos de referencias, ejecuciones de tiempo de desasignación, debemos usar los punteros inteligentes.

Artículo relacionado - C++ Pointer