Articulo de referencia

puntero inteligente

En informática , un puntero inteligente es un tipo de dato abstracto que simula un puntero a la vez que proporciona características adicionales, como la gestión automática de me...

En informática , un puntero inteligente es un tipo de dato abstracto que simula un puntero a la vez que proporciona características adicionales, como la gestión automática de memoria o la comprobación de límites . Estas características están diseñadas para reducir los errores causados ​​por el mal uso de punteros, manteniendo la eficiencia. Los punteros inteligentes suelen llevar un registro de la memoria a la que apuntan y también pueden utilizarse para gestionar otros recursos, como conexiones de red y descriptores de archivos . Los punteros inteligentes se popularizaron por primera vez en el lenguaje de programación C++ durante la primera mitad de la década de 1990 como respuesta a las críticas sobre la falta de recolección automática de basura en C++ . [ 1 ] [ 2 ] Rust , que evita los punteros sin procesar y utiliza la propiedad para determinar la duración de los objetos, también tiene punteros inteligentes.

El uso indebido de punteros puede ser una fuente importante de errores. Los punteros inteligentes previenen la mayoría de las fugas de memoria al automatizar la liberación de memoria. En términos más generales, automatizan la destrucción de objetos : un objeto controlado por un puntero inteligente se destruye automáticamente ( se finaliza y luego se libera) cuando se destruye el último (o único) propietario del objeto, por ejemplo, porque el propietario es una variable local y la ejecución sale del ámbito de la variable . Los punteros inteligentes también eliminan los punteros colgantes al posponer la destrucción hasta que el objeto ya no esté en uso.

Si un lenguaje admite la recolección automática de basura (por ejemplo, Java o C# ), entonces los punteros inteligentes no son necesarios para la recuperación de memoria ni para los aspectos de seguridad de la administración de memoria, pero sí son útiles para otros fines, como la administración de la residencia de estructuras de datos en caché y la administración de recursos de objetos como descriptores de archivos o sockets de red . Java, aunque no tiene una API de punteros, cuenta con conceptos similares para el manejo de referencias.

Existen varios tipos de punteros inteligentes. Algunos funcionan mediante el conteo de referencias , otros asignando la propiedad de un objeto a un puntero.

Historia

Aunque C++ popularizó el concepto de punteros inteligentes, especialmente la variedad con conteo de referencias , [ 3 ] el predecesor inmediato de uno de los lenguajes que inspiraron el diseño de C++ tenía referencias con conteo de referencias integradas en el lenguaje. C++ se inspiró en parte en Simula67 . [ 4 ] El antecesor de Simula67 fue Simula I. En la medida en que el elemento de Simula I es análogo al puntero sin null de C++ , y en la medida en que el proceso de Simula I con una instrucción ficticia como cuerpo de su actividad es análogo a la estructura de C++ (que a su vez es análoga al registro de CAR Hoare en el trabajo contemporáneo de la década de 1960), Simula I tenía elementos con conteo de referencias (es decir, expresiones de puntero que albergan indirección) a procesos (es decir, registros) no más tarde de septiembre de 1965, como se muestra en los párrafos citados a continuación. [ 5 ]

Los procesos pueden referenciarse individualmente. Físicamente, una referencia a un proceso es un puntero a un área de memoria que contiene los datos locales del proceso y cierta información adicional que define su estado de ejecución actual. Sin embargo, por las razones expuestas en la Sección 2.2, las referencias a procesos son siempre indirectas, a través de elementos . Formalmente , una referencia a un proceso es el valor de una expresión de tipo elemento . Los valores de los elementos pueden almacenarse y recuperarse mediante asignaciones, referencias a variables de elementos y otros medios. El lenguaje incluye un mecanismo para hacer accesibles los atributos de un proceso desde fuera, es decir, desde dentro de otros procesos. Esto se denomina acceso remoto. Por lo tanto, un proceso es una estructura de datos referenciable.

Vale la pena destacar la similitud entre un proceso cuyo cuerpo de actividad es una instrucción ficticia y el concepto de registro propuesto recientemente por CAR Hoare y N. Wirth.

Debido a que C++ tomó prestado el enfoque de Simula para la asignación de memoria ( la palabra clave `new` al asignar un proceso/registro para obtener un nuevo elemento para ese proceso/registro ), no es sorprendente que C++ finalmente resucitara también el mecanismo de puntero inteligente con conteo de referencias de Simula dentro de `element` .

Características

En C++ , un puntero inteligente se implementa como una clase plantilla que imita, mediante la sobrecarga de operadores , el comportamiento de un puntero tradicional (sin procesar) , (por ejemplo, desreferenciación, asignación) al tiempo que proporciona características adicionales de gestión de memoria.

Los punteros inteligentes facilitan la programación intencionada al especificar, en el tipo de dato, cómo se gestionará la memoria del objeto al que apunta el puntero. Por ejemplo, si una función de C++ devuelve un puntero, no hay forma de saber si quien la llama debe liberar la memoria del objeto al que apunta una vez que haya terminado de usar la información.

X * funciónambigua (); // ¿Qué se debe hacer con el resultado?

Tradicionalmente, se han utilizado convenciones de nomenclatura para resolver la ambigüedad, [ 6 ] que es un enfoque propenso a errores y laborioso. C++11 introdujo una forma de garantizar una gestión de memoria correcta en este caso declarando que la función devuelva un std::unique_ptr,

usando std :: unique_ptr ;unique_ptr < X > ObviouslyFunction ();

La declaración del tipo de retorno de la función como std::unique_ptrhace explícito el hecho de que quien llama toma posesión del resultado, y el entorno de ejecución de C++ garantiza que la memoria se recuperará automáticamente. Antes de la introducción de en C++11 , se utilizaba std::unique_ptrla clase (que finalmente se eliminó en C++17 ).std::auto_ptr

C++ ofrece los siguientes punteros inteligentes:

  • std::auto_ptr<T>(obsoleto en C++11 y eliminado en C++17) [ 7 ] , un "auto puntero"
  • std::unique_ptr<T, Del>[ 8 ] , un "puntero único"
  • std::shared_ptr<T>[ 9 ] , un "puntero compartido"
  • std::weak_ptr<T>[ 10 ] , un "puntero débil"

En C++26 , se añadió un nuevo puntero, definido en <hazard_pointer>: el puntero de riesgo ( std::hazard_pointer). [ 11 ] Sin embargo, a pesar de su nombre, no es un puntero inteligente, sino una herramienta de concurrencia. Es un puntero de un solo escritor y múltiples lectores que puede ser propiedad de como máximo un hilo en cualquier momento. Para que un objeto sea protegible contra riesgos, debe heredar de std::hazard_pointer_obj_base<T, Del>. El puntero de riesgo ya existía previamente en algunas bibliotecas de terceros. [ 12 ]

Creación de nuevos objetos

Para facilitar la asignación de un std::shared_ptr<T>, C++11 introdujo la función std::make_shared<T>()de fábrica :

usando std :: shared_ptr ;shared_ptr < X > s = std :: make_shared < X > ( /* parámetros del constructor aquí */ );

De manera similar std::unique_ptr<T>, C++14 introdujo la std::make_unique<T>()función de fábrica:

usando std :: unique_ptr ;unique_ptr < X > u = std :: make_unique < X > ( /* parámetros del constructor aquí */ );

En casi todas las circunstancias, se prefiere utilizar estas funciones de fábrica en lugar de la newpalabra clave [ 13 ] , sin embargo, esto no siempre es posible.

Es necesario invocar manualmente el std::unique_ptr<T, Del>constructor para agregar lógica de eliminación personalizada, ya que std::unique_ptr<T, Del>no se puede establecer el Delparámetro de plantilla (que almacena el tipo de eliminador personalizado) a través de std::make_unique<T>()).

using std :: FILE ; using std :: unique_ptr ;unique_ptr < FILE , decltype ( & std :: fclose ) > filePtr ( std :: fopen ( "data.txt" , "r" ), & std :: fclose );

Además, para llamar a constructores privados/protegidos, hay que crear el puntero inteligente manualmente, ya que std::make_unique<T>()solo std::make_shared<T>()se pueden llamar a constructores públicos.

usando std :: unique_ptr ;clase X { privado : int n ;X () = default ; public : static unique_ptr < X > create ( int n ) { return unique_ptr < X > ( new X ( n )); } };

Además, std::make_unique<T>()y std::make_shared<T>()los parámetros se pasan mediante paréntesis en lugar de llaves; por lo tanto, solo se pueden construir agregados y std::initializer_list<T>constructores creando manualmente un puntero inteligente.

using std :: unique_ptr ; using std :: vector ;struct Matrix { vector < double > datos ; tamaño_t filas ; tamaño_t columnas ; };unique_ptr < Matrix > m ( new Matrix { . data = { 1.0 , 0.0 , 0.0 , 0.0 , 1.0 , 0.0 , 0.0 , 0.0 , 1.0 }, . rows = 3 , . cols = 3 });

Punteros únicos

C++11 introduce std::unique_ptr, definido en el encabezado <memory>. [ 14 ]

A std::unique_ptres un contenedor para un puntero sin formato, del cual std::unique_ptrse dice que es propietario. A std::unique_ptrimpide explícitamente la copia de su puntero contenido (como ocurriría con una asignación normal), pero la std::movefunción puede usarse para transferir la propiedad del puntero contenido a otro std::unique_ptr. A std::unique_ptrno puede copiarse porque su constructor de copia y sus operadores de asignación se eliminan explícitamente.

importar std ;usando std :: unique_ptr ;unique_ptr < int > p1 ( new int ( 5 )); unique_ptr < int > p2 = p1 ; // Error de compilación. unique_ptr < int > p3 = std :: move ( p1 ); // Transfiere la propiedad. p3 ahora posee la memoria y p1 se establece en nullptr.p3.reset (); // Borra la memoria. p1.reset ( ) ; // No hace nada.

std::auto_ptrestá obsoleto en C++11 y se eliminó por completo en C++17 . El constructor de copia y los operadores de asignación de std::auto_ptrno copian realmente el puntero almacenado. En cambio, lo transfieren , dejando el objeto anterior std::auto_ptrvacío. Esta era una forma de implementar la propiedad estricta, de modo que solo un std::auto_ptrobjeto puede poseer el puntero en un momento dado. Esto significa que std::auto_ptrno debe usarse donde se necesiten semánticas de copia. [ 15 ] Dado que ya existía con su semántica de copia, no se podía actualizar a un puntero de solo movimiento sin romper la compatibilidad con versiones anteriores con el código existente.std::auto_ptr

El equivalente en Rust de los punteros únicos es std::boxed::Box, que tiene propiedad única de un objeto asignado en el montón. [ 16 ] En versiones anteriores de Rust, también había un std::ptr::Uniqueque envolvía a los no nulos sin procesar *mut T. [ 17 ]

Es posible utilizar " punteros void inteligentes " utilizando std::unique_ptr, utilizando su segundo parámetro de plantilla Del, que almacena un tipo de desasignador (por defecto std::default_delete<T>).

usando std :: unique_ptr ;struct MyDeleter { void operator ()( void * p ) const { delete static_cast < int *> ( p ); } };int main () { unique_ptr < void , MyDeleter > myPointer ( new int ( 42 ), MyDeleter ()); }

Punteros compartidos y punteros débiles

C++11 introduce std::shared_ptry std::weak_ptr, definidos en el encabezado <memory>. [ 14 ] C++11 también introduce std::make_shared( std::make_uniquese introdujo en C++14) para asignar de forma segura memoria dinámica en el paradigma RAII . [ 18 ]

A std::shared_ptres un contenedor para un puntero sin formato . Mantiene la propiedad del conteo de referencias de su puntero contenido en cooperación con todas las copias de std::shared_ptr. Un objeto referenciado por el puntero sin formato contenido se destruirá cuando y solo cuando todas las copias de std::shared_ptrhayan sido destruidas.

importar std ;usando std :: shared_ptr ;shared_ptr < int > p0 ( new int ( 5 )); // Válido, asigna 1 entero y lo inicializa con el valor 5. shared_ptr < int [] > p1 ( new int [ 5 ]); // Válido, asigna 5 enteros. shared_ptr < int [] > p2 = p1 ; // Ahora ambos poseen la memoria.p1.reset (); // La memoria aún existe, debido a p2. p2.reset ( ); // Libera la memoria, ya que nadie más posee la memoria.

A std::weak_ptres un contenedor para un puntero sin formato. Se crea como una copia de un shared_ptr. La existencia o destrucción de std::weak_ptrcopias de un std::shared_ptrno afecta a std::shared_ptrni a sus otras copias. Después de que todas las copias de un std::shared_ptrse hayan destruido, todas std::weak_ptrlas copias quedan vacías.

importar std ;using std :: shared_ptr ; using std :: weak_ptr ;shared_ptr < int > p1 = std :: make_shared < int > ( 5 ); weak_ptr < int > wp1 { p1 }; // p1 posee la memoria.{ shared_ptr < int > p2 = wp1 . lock (); // Ahora p1 y p2 poseen la memoria. // p2 se inicializa desde un puntero débil, por lo que hay que comprobar si la // memoria todavía existe. if ( p2 ) { useSomePointer ( p2 ); } } // p2 se destruye. La memoria es propiedad de p1.p1.reset ( ); // Libera la memoria .shared_ptr < int > p3 = wp1 . lock (); // La memoria se ha agotado, por lo que obtenemos un shared_ptr vacío. if ( p3 ) { // El código no se ejecutará useLivePointer ( p3 ); }

Debido a que la implementación std::shared_ptrutiliza el conteo de referencias , las referencias circulares son potencialmente un problema. Una std::shared_ptrcadena circular se puede romper cambiando el código de modo que una de las referencias sea un std::weak_ptr.

Varios hilos pueden acceder simultáneamente y de forma segura a diferentes objetos std::shared_ptrque std::weak_ptrapuntan al mismo objeto. [ 19 ]

El objeto al que se hace referencia debe protegerse por separado para garantizar la seguridad de los hilos .

std::shared_ptry std::weak_ptrse basan en versiones utilizadas por las bibliotecas Boost . El Informe Técnico 1 (TR1) de C++ las introdujo por primera vez en el estándar como utilidades generales , pero C++11 añade más funciones, en línea con la versión de Boost.

En Java, cada objeto es, en efecto, una referencia compartida, y la Máquina Virtual de Java (JVM) controla la accesibilidad de los objetos. Rust tiene dos tipos de punteros compartidos: std::rc::Rcuno con conteo de referencias y de un solo hilo, y std::sync::Arcotro con conteo de referencias atómico y seguro para hilos.

Java tiene un concepto equivalente a los punteros débiles java.lang.ref.WeakReference(que implementa una referencia débil ), que no aumenta el contador de referencias de un objeto y puede comprobar que un objeto aún existe usando get(). Java de manera similar tiene el java.lang.ref.PhantomReference(que implementa una referencia fantasma ), utilizado con un java.lang.ref.ReferenceQueue, para realizar acciones de limpieza después de que un objeto sea recolectado por el recolector de basura, pero antes de la finalización . A diferencia de java.lang.ref.WeakReference), java.lang.ref.PhantomReferenceno puede acceder realmente al objeto. java.lang.ref.SoftReference, por otro lado, se limpian según la discreción del recolector de basura. Rust tiene un std::rc::Weakpara una referencia débil, no propietaria, que se crea a partir de un std::rc::Rco std::sync::Arcusando downgrade(). [ 20 ]

Otros tipos de punteros inteligentes

Hay otros tipos de punteros inteligentes (que no están en el estándar de C++) implementados en bibliotecas populares de C++ o STL personalizadas , algunos ejemplos incluyen el puntero intrusivo. [ 21 ] [ 22 ]

Véase también

Referencias

  1. Kline, Marshall (septiembre de 1997). "Secciones de C++ FAQs Lite sobre punteros inteligentes con conteo de referencias y semántica de referencias de copia en escritura en las FAQ de gestión de freestore" . cis.usouthal.edu . Recuperado el 6 de abril de 2018 .
  2. Colvin, Gregory (1994). "Propuesta para estandarizar counted_ptr en la biblioteca estándar de C++" (PDF) . open-std.org . Consultado el 6 de abril de 2018 .
  3. Klabnik, Steve; Nichols, Carol (2023) [2018]. "15. Punteros inteligentes". El lenguaje de programación Rust (2.ª ed.). San Francisco, California, EE. UU.: No Starch Press, Inc. págs. 315–351 . ISBN   978-1-7185-0310-6.(xxix+1+527+3 páginas)
  4. Stroustrup, Bjarne. "Una historia de C++: 1979–1991" (PDF) . Consultado el 6 de abril de 2018 .
  5. ^ Dahl, Ole-Johan; Nygaard, Kristen (septiembre de 1966). "SIMULA: un lenguaje de simulación basado en ALGOL" (PDF) . folk.uio.no. ​Consultado el 6 de abril de 2018 .
  6. "Guía de Talent para el diseño de programas, sección Usar nombres especiales para copiar, crear y adoptar rutinas" .
  7. cppreference.com. "std::auto_ptr" . cppreference.com . cppreference.com . Consultado el 11 de julio de 2026 .
  8. cppreference.com. "std::unique_ptr" . cppreference.com . cppreference.com . Consultado el 11 de julio de 2026 .
  9. cppreference.com. "std::shared_ptr" . cppreference.com . cppreference.com . Consultado el 11 de julio de 2026 .
  10. cppreference.com. "std::weak_ptr" . cppreference.com . cppreference.com . Consultado el 11 de julio de 2026 .
  11. cppreference.com. "Encabezado de biblioteca estándar <hazard_pointer>" . cppreference.com . cppreference.com . Consultado el 11 de julio de 2026 .
  12. "folly/Hazptr.h en main · facebook/folly" . github.com .
  13. Sutter, Herb (2013-04-20). "Informe de viaje: Reunión de primavera de ISO C++ 2013" . isocpp.org . Recuperado el 14 de junio de 2013 .
  14. 1 2 ISO 14882:2011 20.7.1
  15. Estándar de codificación segura CERT C++
  16. "Box en std::boxed" . doc.rust-lang.org . 2025-08-04.
  17. "std::ptr::Unique" . web.mit.edu . 2025-09-11.
  18. ISO 14882:2014 20.7.1
  19. "Seguridad de subprocesos de boost::shared_ptr" .(Nota: No cubre formalmente std::shared_ptr, pero se cree que tiene las mismas limitaciones de subprocesos).
  20. "Débil en std::rc" . doc.rust-lang.org . 2025-08-04.
  21. "Boost.SmartPtr: La biblioteca de punteros inteligentes - 1.81.0" . boost.org .
  22. "EASTL/intrusive_ptr.h en master · electronicarts/EASTL" . github.com .

Lecturas adicionales

  • countptr.hpp . La biblioteca estándar de C++: un tutorial y referencia por Nicolai M. Josuttis
  • Punteros inteligentes de Boost
  • Punteros inteligentes en Delphi
  • Punteros inteligentes en Rust
  • Punteros inteligentes en C++ moderno