Punteros inteligentes en c++: garantizando la gestión de memoria y recursos

En la programación moderna de C++, la biblioteca estándar incluye punteros inteligentes, los cuales se utilizan para garantizar que los programas estén libres de fugas de memoria y recursos, y sean seguros ante excepciones.

Índice
  1. Usos de los punteros inteligentes
  2. Tipos de punteros inteligentes
    1. Punteros inteligentes de la biblioteca estándar de C++
    2. Punteros inteligentes para objetos COM (programación clásica de Windows)
    3. Punteros inteligentes de ATL para objetos POCO

Usos de los punteros inteligentes

Los punteros inteligentes están definidos en el espacio de nombres std en el archivo de encabezado <memory>. Son fundamentales para el idiom de programación RAII (Resource Acquisition Is Initialization). El objetivo principal de este idiom es asegurar que la adquisición de recursos ocurra al mismo tiempo que la inicialización del objeto, de manera que todos los recursos del objeto se creen y estén listos en una sola línea de código. En términos prácticos, el principio principal del RAII es dar la propiedad de cualquier recurso asignado en el montón, como memoria asignada dinámicamente o manejadores de objetos del sistema, a un objeto asignado en la pila cuyo destructor contiene el código para eliminar o liberar el recurso y cualquier código de limpieza asociado.

En la mayoría de los casos, cuando inicializas un puntero sin procesar o un manejador de recursos para que apunte a un recurso real, pasa el puntero a un puntero inteligente de inmediato. En C++ moderno, los punteros sin procesar solo se utilizan en bloques de código pequeños de alcance limitado, bucles o funciones auxiliares donde el rendimiento es crítico y no hay posibilidad de confusión sobre la propiedad.

El siguiente ejemplo compara una declaración de puntero sin procesar con una declaración de puntero inteligente.

void UseRawPointer() { // Usando un puntero sin procesar -- no recomendado. Song* pSong = new Song(L nada en ti, L chayanne ); // Utilizar pSong... // ¡No olvidar eliminar! delete pSong;}void UseSmartPointer() { // Declarar un puntero inteligente en la pila y pasarle el puntero sin procesar. std::unique_ptr<Song> song2(new Song(L nada en ti, L chayanne )); // Utilizar song.. std::wstring s = song2->duration_; //...}// song2 se elimina automáticamente aquí.

Como se muestra en el ejemplo, un puntero inteligente es una plantilla de clase que se declara en la pila e inicializa utilizando un puntero sin procesar que apunta a un objeto asignado en el montón. Después de que el puntero inteligente se inicializa, este es dueño del puntero sin procesar. Esto significa que el puntero inteligente es responsable de eliminar la memoria que el puntero sin procesar especifica. El destructor del puntero inteligente contiene la llamada a delete, y debido a que el puntero inteligente se declara en la pila, su destructor se invoca cuando el puntero inteligente queda fuera de alcance, incluso si se produce una excepción en algún lugar más arriba en la pila.

Accede al puntero encapsulado utilizando los operadores de puntero familiares, -> y *, los cuales la clase del puntero inteligente sobrecarga para devolver el puntero sin procesar encapsulado.

El idiom de puntero inteligente en C++ se asemeja a la creación de objetos en lenguajes como C#: creas el objeto y luego dejas que el sistema se encargue de eliminarlo en el momento correcto. La diferencia es que no se ejecuta un recolector de basura separado en segundo plano; la memoria se gestiona a través de las reglas de ámbito estándar de C++ para que el entorno de ejecución sea más rápido y eficiente.

Importante: Crea siempre punteros inteligentes en una línea de código separada, nunca en una lista de parámetros, para evitar fugas sutiles de recursos debido a ciertas reglas de asignación de listas de parámetros.

El siguiente ejemplo muestra cómo se podría utilizar un puntero inteligente del tipo unique_ptr de la biblioteca estándar de C++ para encapsular un puntero a un objeto grande.

punteros inteligentes c++ - Cuál es la ventaja de utilizar punteros inteligentes en C++

class LargeObject {public: void DoSomething(){}};void ProcessLargeObject(const LargeObject& lo){}void SmartPointerDemo() { // Crea el objeto y pásalo a un puntero inteligente std::unique_ptr<LargeObject> pLarge(new LargeObject()); // Llama a un método en el objeto pLarge->DoSomething(); // Pasa una referencia a un método ProcessLargeObject(*pLarge);}// pLarge se elimina automáticamente cuando el bloque de función queda fuera de alcance.

El ejemplo muestra los siguientes pasos esenciales para utilizar punteros inteligentes.

  1. Declara el puntero inteligente como una variable automática (local). (No uses la expresión new o malloc en el propio puntero inteligente.)
  2. En el parámetro de tipo, especifica el tipo apuntado por el puntero encapsulado.
  3. Pasa un puntero sin procesar a un objeto creado con new al constructor del puntero inteligente. (Algunas funciones de utilidad o constructores de punteros inteligentes hacen esto por ti.)
  4. Utiliza los operadores sobrecargados -> y * para acceder al objeto.
  5. Deja que el puntero inteligente elimine el objeto.

Los punteros inteligentes están diseñados para ser lo más eficientes posible tanto en términos de memoria como de rendimiento. Por ejemplo, el único miembro de datos en unique_ptr es el puntero encapsulado. Esto significa que unique_ptr tiene exactamente el mismo tamaño que ese puntero, ya sea de cuatro o de ocho bytes. Acceder al puntero encapsulado utilizando los operadores sobrecargados * y -> del puntero inteligente no es significativamente más lento que acceder directamente a los punteros sin procesar.

Los punteros inteligentes tienen sus propias funciones miembro, a las cuales se accede utilizando la notación de punto. Por ejemplo, algunos punteros inteligentes de la biblioteca estándar de C++ tienen una función miembro reset que libera la propiedad del puntero. Esto es útil cuando quieres liberar la memoria propiedad del puntero inteligente antes de que el puntero inteligente quede fuera de alcance, como se muestra en el siguiente ejemplo.

void SmartPointerDemo2() { // Crea el objeto y pásalo a un puntero inteligente std::unique_ptr<LargeObject> pLarge(new LargeObject()); // Llama a un método en el objeto pLarge->DoSomething(); // Libera la memoria antes de salir del bloque de función pLarge.reset(); // Haz algún otro trabajo...}

Los punteros inteligentes suelen proporcionar una forma de acceder directamente a su puntero sin procesar. Los punteros inteligentes de la biblioteca estándar de C++ tienen una función miembro get para este propósito, y CComPtr tiene un miembro de clase pública p. Al proporcionar acceso directo al puntero subyacente, puedes utilizar el puntero inteligente para administrar la memoria en tu propio código y aún así pasar el puntero sin procesar a código que no admita punteros inteligentes.

void SmartPointerDemo4() { // Crea el objeto y pásalo a un puntero inteligente std::unique_ptr<LargeObject> pLarge(new LargeObject()); // Llama a un método en el objeto pLarge->DoSomething(); // Pasa el puntero sin procesar a una API heredada LegacyLargeObjectFunction(pLarge.get());}

Tipos de punteros inteligentes

La siguiente sección resume los diferentes tipos de punteros inteligentes disponibles en el entorno de programación de Windows y describe cuándo utilizarlos.

Punteros inteligentes de la biblioteca estándar de C++

Utiliza estos punteros inteligentes como primera opción para encapsular punteros a objetos de C++ ordinarios (POCO).

  • unique_ptr : Permite tener un único propietario del puntero subyacente. Utilízalo como elección predeterminada para POCO a menos que sepas con certeza que necesitas un shared_ptr. Puede ser movido a un nuevo propietario, pero no copiado ni compartido. Reemplaza a auto_ptr, que está en desuso. Se compara con boost::scoped_ptr. unique_ptr es pequeño y eficiente; su tamaño es de un puntero y admite referencias rvalue para una inserción y recuperación rápida desde las colecciones de la biblioteca estándar de C++. Archivo de encabezado: <memory>.
  • shared_ptr : Puntero inteligente con conteo de referencias. Utiliza cuando quieres asignar un único puntero sin procesar a múltiples propietarios, por ejemplo, cuando devuelves una copia de un puntero desde un contenedor pero quieres mantener el original. El puntero sin procesar no se elimina hasta que todos los propietarios de shared_ptr hayan quedado fuera de alcance o hayan cedido la propiedad de otra manera. Su tamaño es de dos punteros: uno para el objeto y otro para el bloque de control compartido que contiene el conteo de referencias. Archivo de encabezado: <memory>.
  • weak_ptr : Puntero inteligente para casos especiales que se utiliza en conjunción con shared_ptr. Un weak_ptr proporciona acceso a un objeto que es propiedad de uno o más instancias de shared_ptr, pero no participa en el conteo de referencias. Utilízalo cuando quieres observar un objeto, pero no requieres que permanezca vivo. Es necesario en algunos casos para romper referencias circulares entre instancias de shared_ptr. Archivo de encabezado: <memory>.

Punteros inteligentes para objetos COM (programación clásica de Windows)

Cuando trabajas con objetos COM, envuelve los punteros de interfaz en un tipo de puntero inteligente apropiado. La biblioteca de plantillas activas (ATL) define varios punteros inteligentes para diversos propósitos. También puedes utilizar el tipo de puntero inteligente _com_ptr_t, que el compilador utiliza al crear clases de envoltura a partir de archivos .tlb. Es la mejor opción cuando no deseas incluir los archivos de encabezado de ATL.

  • CComPtr : Utiliza esto a menos que no puedas utilizar ATL. Realiza el conteo de referencias utilizando los métodos AddRef y Release. Archivo de encabezado: <atlbase.h>.
  • CComQIPtr : Se asemeja a CComPtr pero también proporciona una sintaxis simplificada para llamar a QueryInterface en objetos COM. Archivo de encabezado: <atlbase.h>.
  • CComHeapPtr : Puntero inteligente para objetos que utilizan CoTaskMemFree para liberar memoria.
  • CComGITPtr : Puntero inteligente para interfaces que se obtienen de la tabla de interfaces globales (GIT).
  • _com_ptr_t : Se asemeja a CComQIPtr en funcionalidad, pero no depende de los archivos de encabezado de ATL.

Punteros inteligentes de ATL para objetos POCO

Además de los punteros inteligentes para objetos COM, ATL también define punteros inteligentes y colecciones de punteros inteligentes para objetos de C++ ordinarios (POCO). En la programación clásica de Windows, estos tipos son alternativas útiles a las colecciones de la biblioteca estándar de C++, especialmente cuando no se requiere portabilidad de código o cuando no se desea mezclar los modelos de programación de la biblioteca estándar de C++ y ATL.

  • CAutoPtr : Puntero inteligente que garantiza una propiedad única mediante la transferencia de propiedad en la copia. Comparables a la clase std::auto_ptr desaprobada.
  • CHeapPtr : Puntero inteligente para objetos que se asignan utilizando la función C malloc.
  • CAutoVectorPtr : Puntero inteligente para matrices que se asignan utilizando new[].
  • CAutoPtrArray : Clase que encapsula una matriz de elementos CAutoPtr.
  • CAutoPtrList : Clase que encapsula métodos para manipular una lista de nodos CAutoPtr.

Los punteros inteligentes en C++ son una herramienta poderosa para garantizar la gestión adecuada de la memoria y los recursos en un programa. Al utilizar punteros inteligentes, puedes evitar fugas de memoria y errores relacionados con la liberación incorrecta de recursos. Además, los punteros inteligentes simplifican el código al automatizar la gestión de memoria y al proporcionar un mecanismo de limpieza automática. Esto hace que el código sea más seguro y más fácil de mantener en el largo plazo.

Si quieres conocer otras notas parecidas a Punteros inteligentes en c++: garantizando la gestión de memoria y recursos puedes visitar la categoría Inteligencia.

Subir