Punteros inteligentes (C++ moderno)

En la programación del lenguaje C++ actual, la biblioteca estándar incluye punteros inteligentes, que se utilizan para asegurarse de que los programas están libres de memoria y de pérdidas de recursos y son seguros ante excepciones.

Usos de los punteros inteligentes

Los punteros inteligentes se definen en el espacio de nombres std del archivo de encabezado <memory>. Son cruciales para el modismo de programación Resource Acquisition Is Initialization(RAII). El objetivo principal de esta expresión es asegurarse de que la adquisición de recursos se produce al mismo tiempo que se inicializa el objeto. Todos los recursos del objeto se crean y se preparan en una línea de código.

En términos prácticos, el principio fundamental de RAII es asignar la propiedad de cualquier recurso asignado en el montón a un objeto alojado en la pila, cuyo destructor contiene el código para eliminar o liberar el recurso, así como cualquier código de limpieza asociado. Dichos objetos incluyen memoria asignada dinámicamente o identificadores de objetos del sistema.

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

En el ejemplo siguiente se compara una declaración de puntero sin formato con una declaración de puntero inteligente.

void UseRawPointer()
{
    // Using a raw pointer -- not recommended.
    Song* pSong = new Song(L"Nothing on You", L"Bruno Mars"); 

    // Use pSong...

    // Don't forget to delete!
    delete pSong;   
}

void UseSmartPointer()
{
    // Declare a smart pointer on stack and pass it the raw pointer.
    unique_ptr<Song> song2(new Song(L"Nothing on You", L"Bruno Mars"));

    // Use song2...
    wstring s = song2->duration_;
    //...

} // song2 is deleted automatically here.

Como se muestra en el ejemplo, un puntero inteligente es una plantilla de clase que se declara en la pila y se inicializa mediante un puntero sin procesar que apunta a un objeto asignado en el montón. Una vez que se inicializa el puntero inteligente, se convierte en propietario del puntero sin formato. Este enfoque significa que el puntero inteligente es responsable de eliminar la memoria que especifica el puntero sin procesar.

El destructor de puntero inteligente contiene la llamada a delete. Dado que el puntero inteligente se declara en la pila, se invoca su destructor cuando el puntero inteligente queda fuera de ámbito. Se invoca incluso si se produce una excepción en algún lugar más arriba de la pila.

Acceda al puntero encapsulado mediante los conocidos operadores de puntero: -> y *. La clase de puntero inteligente sobrecarga estos operadores para devolver el puntero bruto encapsulado.

La expresión del puntero inteligente de C++ se parece a la creación de objetos en lenguajes como C#. Cree el objeto y, a continuación, deje que el sistema se ocupe de eliminarlo en el momento correcto. La diferencia es que no se ejecuta en segundo plano ningún recolector de basura por separado. La memoria se administra a través de las reglas estándar de ámbito de C++ para que el entorno de tiempo de ejecución sea más rápido y eficaz.

Importante

Cree siempre punteros inteligentes en una línea de código independiente, nunca en una lista de parámetros. Este enfoque evita una pérdida sutil de recursos debido a ciertas reglas de asignación de listas de parámetros.

En el ejemplo siguiente se muestra cómo se puede utilizar un tipo de puntero inteligente unique_ptr de la biblioteca estándar de C++ para encapsular un puntero a un objeto grande.

class LargeObject
{
public:
    void DoSomething(){}
};

void ProcessLargeObject(const LargeObject& lo){}
void SmartPointerDemo()
{    
    // Create the object and pass it to a smart pointer
    std::unique_ptr<LargeObject> pLarge(new LargeObject());

    //Call a method on the object
    pLarge->DoSomething();

    // Pass a reference to a method.
    ProcessLargeObject(*pLarge);

} //pLarge is deleted automatically when function block goes out of scope.

En el ejemplo se muestran los pasos básicos siguientes para utilizar punteros inteligentes.

  1. Declare el puntero inteligente como variable automática (local). No use la expresión new o malloc sobre el propio puntero inteligente.

  2. En el parámetro de tipo, especifique el tipo al que apunta el puntero encapsulado.

  3. Pase un puntero sin procesar a un objeto al que se le ha aplicado new en el constructor del puntero inteligente. Algunas funciones utilitarias o constructores de punteros inteligentes llevan a cabo esta operación por usted.

  4. Utilice los operadores sobrecargados -> y * para tener acceso al objeto.

  5. Deje que el puntero inteligente elimine el objeto.

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

Los punteros inteligentes tienen sus propias funciones miembro, a las que se accede mediante la notación de puntos. Por ejemplo, algunos punteros inteligentes de la biblioteca estándar de C++ disponen de una función miembro reset que cede la propiedad del puntero. Este punto es útil si desea liberar la memoria gestionada por el puntero inteligente antes de que el puntero inteligente salga de su ámbito, como se muestra en el siguiente ejemplo.

void SmartPointerDemo2()
{
    // Create the object and pass it to a smart pointer
    std::unique_ptr<LargeObject> pLarge(new LargeObject());

    //Call a method on the object
    pLarge->DoSomething();

    // Free the memory before we exit function block.
    pLarge.reset();

    // Do some other work...

}

Los punteros inteligentes suelen proporcionar una forma de acceder directamente a su puntero bruto. Los punteros inteligentes de la biblioteca estándar de C++ tienen una get función miembro para este propósito. CComPtr tiene un miembro público de la clase p. Al proporcionar acceso directo al puntero subyacente, puede usar el puntero inteligente para administrar la memoria en su propio código y seguir pasando el puntero no administrado a código que no admite punteros inteligentes.

void SmartPointerDemo4()
{
    // Create the object and pass it to a smart pointer
    std::unique_ptr<LargeObject> pLarge(new LargeObject());

    //Call a method on the object
    pLarge->DoSomething();

    // Pass raw pointer to a legacy API
    LegacyLargeObjectFunction(pLarge.get());    
}

Tipos de punteros inteligentes

En la sección siguiente se resumen los distintos tipos de punteros inteligentes disponibles en el entorno de programación de Windows y se describe cuándo utilizarlos.

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

Utilice estos punteros inteligentes como primera opción para encapsular punteros a objetos C++ simples (POCO).

  • unique_ptr

    Permite exactamente un único propietario del puntero subyacente. Úselo como opción predeterminada para POCO, a menos que sepa con certeza que necesita un shared_ptr. Puede moverse a un nuevo propietario, pero no se puede copiar ni compartir. Sustituye a auto_ptr, que está desusado. Comparado con boost::scoped_ptr,

    unique_ptr es pequeño y eficiente. El tamaño es un puntero y admite referencias rvalue para la rápida inserción y recuperación de colecciones de bibliotecas estándar de C++. Archivo de encabezado: <memory>. Para obtener más información, consulte Cómo: Crear y usar instancias de unique_ptr y Clase unique_ptr.

  • shared_ptr

    Puntero inteligente con recuento de referencias. Úselo cuando desee asignar un puntero sin procesar a varios propietarios. Por ejemplo, puede devolver una copia de un puntero almacenado en un contenedor y conservar el original. El puntero sin formato no se elimina hasta que todos los propietarios de shared_ptr salgan de su ámbito o renuncien a la propiedad.

    El tamaño consta de dos punteros: uno para el objeto y otro para el bloque de control compartido que contiene el recuento de referencias. Archivo de encabezado: <memory>. Para obtener más información, consulte Cómo: Crear y usar instancias de shared_ptr y Clase shared_ptr.

  • weak_ptr

    Puntero inteligente para casos especiales para usar con shared_ptr. Un weak_ptr proporciona acceso a un objeto del que son propietarias una o varias instancias de shared_ptr, pero no participa en el recuento de referencias. Úselo cuando quiera observar un objeto, pero no necesite que siga existiendo. Necesario en algunos casos para romper las referencias circulares entre instancias de shared_ptr.

    Archivo de encabezado: <memory>. Para obtener más información, consulte Cómo: Crear y usar instancias de weak_ptr y Clase weak_ptr.

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

Cuando trabaje con objetos COM, encapsule los punteros de interfaz en un tipo de puntero inteligente adecuado. Active Template Library (ATL) define varios punteros inteligentes para propósitos diferentes. También puede usar el tipo de puntero inteligente _com_ptr_t, que el compilador utiliza cuando crea clases envoltorio a partir de archivos .tlb. Es la mejor opción cuando no desea incluir los archivos de encabezado ATL.

Punteros inteligentes ATL para objetos POCO

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

  • Clase CAutoPtr

    Puntero inteligente que garantiza la propiedad exclusiva transfiriendo la propiedad al copiarlo. Puede compararse con la clase std::auto_ptr en desuso.

  • Clase CHeapPtr

    Puntero inteligente para objetos asignados mediante la función de C malloc.

  • Clase CAutoVectorPtr

    Puntero inteligente para matrices asignadas mediante new[].

  • Clase CAutoPtrArray

    Clase que encapsula una matriz de elementos CAutoPtr.

  • Clase CAutoPtrList

    Clase que encapsula los métodos para manipular una lista de nodos de CAutoPtr.

Consulte también