¿Qué es un puntero inteligente y cuándo debo usar uno?
¿Qué es un puntero inteligente y cuándo debo usar uno?
Respuestas:
ACTUALIZAR
Esta respuesta es bastante antigua, por lo que describe lo que era "bueno" en ese momento, que eran los punteros inteligentes proporcionados por la biblioteca Boost. Puesto que C ++ 11, la biblioteca estándar ha proporcionado suficientes tipos punteros inteligentes, y por lo que debería favorecer el uso de std::unique_ptr
, std::shared_ptr
y std::weak_ptr
.
También había std::auto_ptr
. Era muy parecido a un puntero de alcance, excepto que también tenía la habilidad peligrosa "especial" de ser copiada, lo que también transfiere la propiedad inesperadamente.
Estaba en desuso en C ++ 11 y se eliminó en C ++ 17 , por lo que no debe usarlo.
std::auto_ptr<MyObject> p1 (new MyObject());
std::auto_ptr<MyObject> p2 = p1; // Copy and transfer ownership.
// p1 gets set to empty!
p2->DoSomething(); // Works.
p1->DoSomething(); // Oh oh. Hopefully raises some NULL pointer exception.
ANTIGUA RESPUESTA
Un puntero inteligente es una clase que envuelve un puntero C ++ 'crudo' (o 'desnudo'), para administrar la vida útil del objeto al que se apunta. No existe un único tipo de puntero inteligente, pero todos intentan abstraer un puntero sin formato de manera práctica.
Se deben preferir los punteros inteligentes a los punteros en bruto. Si siente que necesita usar punteros (primero considere si realmente lo hace), normalmente querrá usar un puntero inteligente ya que esto puede aliviar muchos de los problemas con punteros sin procesar, principalmente olvidando eliminar el objeto y la pérdida de memoria.
Con punteros en bruto, el programador tiene que destruir explícitamente el objeto cuando ya no es útil.
// Need to create the object to achieve some goal
MyObject* ptr = new MyObject();
ptr->DoSomething(); // Use the object in some way
delete ptr; // Destroy the object. Done with it.
// Wait, what if DoSomething() raises an exception...?
Un puntero inteligente, en comparación, define una política sobre cuándo se destruye el objeto. Todavía tiene que crear el objeto, pero ya no tiene que preocuparse por destruirlo.
SomeSmartPtr<MyObject> ptr(new MyObject());
ptr->DoSomething(); // Use the object in some way.
// Destruction of the object happens, depending
// on the policy the smart pointer class uses.
// Destruction would happen even if DoSomething()
// raises an exception
La política más simple en uso implica el alcance del objeto contenedor de puntero inteligente, como el implementado por boost::scoped_ptr
ostd::unique_ptr
.
void f()
{
{
std::unique_ptr<MyObject> ptr(new MyObject());
ptr->DoSomethingUseful();
} // ptr goes out of scope --
// the MyObject is automatically destroyed.
// ptr->Oops(); // Compile error: "ptr" not defined
// since it is no longer in scope.
}
Tenga en cuenta que las std::unique_ptr
instancias no se pueden copiar. Esto evita que el puntero se elimine varias veces (incorrectamente). Sin embargo, puede pasar referencias a otras funciones que llame.
std::unique_ptr
s son útiles cuando desea vincular la vida útil del objeto a un bloque de código en particular, o si lo incrusta como datos de miembros dentro de otro objeto, la vida útil de ese otro objeto. El objeto existe hasta que se sale del bloque de código que lo contiene, o hasta que el objeto que lo contiene se destruye.
Una política de puntero inteligente más compleja implica el recuento de referencias del puntero. Esto permite copiar el puntero. Cuando se destruye la última "referencia" al objeto, se elimina el objeto. Esta política es implementada por boost::shared_ptr
y std::shared_ptr
.
void f()
{
typedef std::shared_ptr<MyObject> MyObjectPtr; // nice short alias
MyObjectPtr p1; // Empty
{
MyObjectPtr p2(new MyObject());
// There is now one "reference" to the created object
p1 = p2; // Copy the pointer.
// There are now two references to the object.
} // p2 is destroyed, leaving one reference to the object.
} // p1 is destroyed, leaving a reference count of zero.
// The object is deleted.
Los punteros contados de referencia son muy útiles cuando la vida útil de su objeto es mucho más complicada y no está vinculada directamente a una sección particular de código ni a otro objeto.
Hay un inconveniente para hacer referencia a punteros contados: la posibilidad de crear una referencia colgante:
// Create the smart pointer on the heap
MyObjectPtr* pp = new MyObjectPtr(new MyObject())
// Hmm, we forgot to destroy the smart pointer,
// because of that, the object is never destroyed!
Otra posibilidad es crear referencias circulares:
struct Owner {
std::shared_ptr<Owner> other;
};
std::shared_ptr<Owner> p1 (new Owner());
std::shared_ptr<Owner> p2 (new Owner());
p1->other = p2; // p1 references p2
p2->other = p1; // p2 references p1
// Oops, the reference count of of p1 and p2 never goes to zero!
// The objects are never destroyed!
Para evitar este problema, tanto Boost como C ++ 11 han definido a weak_ptr
para definir una referencia débil (sin contar) a a shared_ptr
.
std::auto_ptr<MyObject> p1 (new MyObject());
lugar de std::auto_ptr<MyObject> p1 (new Owner());
?
const std::auto_ptr
es seguro de usar, si está atascado con C ++ 03. Lo usé bastante para el patrón Pimpl hasta que obtuve acceso a C ++ 11.
Aquí hay una respuesta simple para estos días de C ++ moderno (C ++ 11 y posterior):
std::unique_ptr
cuando no tenga la intención de mantener múltiples referencias al mismo objeto. Por ejemplo, úselo para un puntero a la memoria que se asigna al ingresar algún alcance y se desasigna al salir del alcance.std::shared_ptr
cuando desee referirse a su objeto desde múltiples lugares, y no desee que su objeto sea desasignado hasta que todas estas referencias hayan desaparecido.std::weak_ptr
cuando quiera referirse a su objeto desde múltiples lugares, para aquellas referencias para las cuales está bien ignorar y desasignar (para que solo noten que el objeto se ha ido cuando intenta desreferenciar).boost::
punteros inteligentes o std::auto_ptr
excepto en casos especiales en los que pueda leer si es necesario.T*
es std::unique_ptr<T>
lo que std::weak_ptr<T>
esstd::shared_ptr<T>
El puntero inteligente es un tipo de puntero con alguna funcionalidad adicional, por ejemplo, desasignación automática de memoria, recuento de referencias, etc.
Hay una pequeña introducción disponible en la página Punteros inteligentes: ¿qué, por qué y cuál? .
Uno de los tipos simples de puntero inteligente es std::auto_ptr
(capítulo 20.4.5 del estándar C ++), que permite desasignar memoria automáticamente cuando está fuera de alcance y que es más robusto que el uso simple de puntero cuando se lanzan excepciones, aunque menos flexible.
Otro tipo conveniente es el boost::shared_ptr
que implementa el recuento de referencias y desasigna automáticamente la memoria cuando no quedan referencias al objeto. Esto ayuda a evitar pérdidas de memoria y es fácil de usar para implementar RAII .
El tema se trata en profundidad en el libro "Plantillas C ++: La guía completa" de David Vandevoorde, Nicolai M. Josuttis , capítulo Capítulo 20. Punteros inteligentes. Algunos temas cubiertos:
std::auto_ptr
es obsoleta y altamente desalentadora, ya que puede transferir accidentalmente la propiedad. - C ++ 11 elimina la necesidad de Boost, uso: std::unique_ptr
, std::shared_ptr
ystd::weak_ptr
Las definiciones proporcionadas por Chris, Sergdev y Llyod son correctas. Sin embargo, prefiero una definición más simple, solo para simplificar mi vida: un puntero inteligente es simplemente una clase que sobrecarga los operadores ->
y *
. Lo que significa que su objeto se parece semánticamente a un puntero, pero puede hacerlo hacer cosas mucho más geniales, incluido el recuento de referencias, la destrucción automática, etc.
shared_ptr
y auto_ptr
son suficientes en la mayoría de los casos, pero vienen con su propio conjunto de pequeñas idiosincrasias.
Un puntero inteligente es como un puntero normal (mecanografiado), como "char *", excepto cuando el puntero se sale del alcance y lo que señala también se elimina. Puede usarlo como lo haría con un puntero normal, usando "->", pero no si necesita un puntero real a los datos. Para eso, puede usar "& * ptr".
Es útil para:
Objetos que deben asignarse con nuevos, pero que le gustaría tener la misma vida útil que algo en esa pila. Si el objeto se asigna a un puntero inteligente, se eliminarán cuando el programa salga de esa función / bloque.
Miembros de las clases de datos, de modo que cuando se elimina el objeto, también se eliminan todos los datos de propiedad, sin ningún código especial en el destructor (deberá asegurarse de que el destructor sea virtual, lo que casi siempre es algo bueno) .
Es posible que no desee utilizar un puntero inteligente cuando:
Ver también:
La mayoría de los tipos de punteros inteligentes manejan la disposición del objeto puntero a usted. Es muy útil porque ya no tiene que pensar en deshacerse de los objetos manualmente.
Los punteros inteligentes más comúnmente utilizadas son std::tr1::shared_ptr
(o boost::shared_ptr
), y, con menor frecuencia, std::auto_ptr
. Recomiendo el uso regular de shared_ptr
.
shared_ptr
es muy versátil y se ocupa de una gran variedad de escenarios de eliminación, incluidos los casos en que los objetos deben "pasar a través de los límites de la DLL" (el caso común de pesadilla si libc
se utilizan diferentes s entre su código y las DLL).
Un puntero inteligente es un objeto que actúa como un puntero, pero que además proporciona control sobre la construcción, destrucción, copia, movimiento y desreferenciación.
Uno puede implementar su propio puntero inteligente, pero muchas bibliotecas también proporcionan implementaciones de puntero inteligente, cada una con diferentes ventajas y desventajas.
Por ejemplo, Boost proporciona las siguientes implementaciones de puntero inteligente:
shared_ptr<T>
es un puntero para T
utilizar un recuento de referencia para determinar cuándo ya no se necesita el objeto.scoped_ptr<T>
es un puntero que se elimina automáticamente cuando sale del alcance. Ninguna asignación es posible.intrusive_ptr<T>
es otro puntero de recuento de referencia. Proporciona un mejor rendimiento que shared_ptr
, pero requiere que el tipo T
proporcione su propio mecanismo de conteo de referencia.weak_ptr<T>
es un puntero débil, que trabaja en conjunto shared_ptr
para evitar referencias circulares.shared_array<T>
es como shared_ptr
, pero para matrices de T
.scoped_array<T>
es como scoped_ptr
, pero para matrices de T
.Estas son solo una descripción lineal de cada una y se pueden usar según las necesidades, para obtener más detalles y ejemplos, puede consultar la documentación de Boost.
Además, la biblioteca estándar de C ++ proporciona tres punteros inteligentes; std::unique_ptr
para propiedad única, std::shared_ptr
para propiedad compartida y std::weak_ptr
. std::auto_ptr
existía en C ++ 03 pero ahora está en desuso.
scoped_ptr
no es como una declaración local const unique_ptr
, que también se elimina al salir del ámbito.
Aquí está el enlace para respuestas similares: http://sickprogrammersarea.blogspot.in/2014/03/technical-interview-questions-on-c_6.html
Un puntero inteligente es un objeto que actúa, se ve y se siente como un puntero normal, pero ofrece más funcionalidad. En C ++, los punteros inteligentes se implementan como clases de plantillas que encapsulan un puntero y anulan los operadores de puntero estándar. Tienen una serie de ventajas sobre los punteros regulares. Se garantiza que se inicializarán como punteros nulos o punteros a un objeto de montón. Se verifica la indirección a través de un puntero nulo. No es necesario borrar nunca. Los objetos se liberan automáticamente cuando el último puntero hacia ellos se ha ido. Un problema importante con estos punteros inteligentes es que, a diferencia de los punteros regulares, no respetan la herencia. Los punteros inteligentes no son atractivos para el código polimórfico. A continuación se muestra un ejemplo para la implementación de punteros inteligentes.
Ejemplo:
template <class X>
class smart_pointer
{
public:
smart_pointer(); // makes a null pointer
smart_pointer(const X& x) // makes pointer to copy of x
X& operator *( );
const X& operator*( ) const;
X* operator->() const;
smart_pointer(const smart_pointer <X> &);
const smart_pointer <X> & operator =(const smart_pointer<X>&);
~smart_pointer();
private:
//...
};
Esta clase implementa un puntero inteligente a un objeto de tipo X. El objeto en sí está ubicado en el montón. Aquí está cómo usarlo:
smart_pointer <employee> p= employee("Harris",1333);
Al igual que otros operadores sobrecargados, p se comportará como un puntero normal,
cout<<*p;
p->raise_salary(0.5);
http://en.wikipedia.org/wiki/Smart_pointer
En ciencias de la computación, un puntero inteligente es un tipo de datos abstracto que simula un puntero mientras proporciona características adicionales, como la recolección automática de basura o la verificación de límites. Estas características adicionales están destinadas a reducir los errores causados por el mal uso de los punteros mientras se mantiene la eficiencia. Los punteros inteligentes generalmente realizan un seguimiento de los objetos que los apuntan con el propósito de administrar la memoria. El mal uso de los punteros es una fuente importante de errores: la asignación constante, la desasignación y la referencia que debe realizar un programa escrito con punteros hace que sea muy probable que ocurran algunas pérdidas de memoria. Los punteros inteligentes intentan evitar pérdidas de memoria al hacer que la desasignación de recursos sea automática: cuando se destruye el puntero a un objeto (o el último de una serie de punteros),
Deje que T sea una clase en este tutorial Los punteros en C ++ se pueden dividir en 3 tipos:
1) Punteros crudos :
T a;
T * _ptr = &a;
Mantienen una dirección de memoria en una ubicación en la memoria. Úselo con precaución, ya que los programas se vuelven complejos y difíciles de seguir.
Punteros con dirección o datos constantes {Leer al revés}
T a ;
const T * ptr1 = &a ;
T const * ptr1 = &a ;
Puntero a un tipo de datos T que es una constante. Lo que significa que no puede cambiar el tipo de datos con el puntero. es decir *ptr1 = 19
; no trabajará. Pero puedes mover el puntero. es decir ptr1++ , ptr1--
; etc funcionará Leer al revés: puntero para escribir T, que es constante
T * const ptr2 ;
Un puntero constante a un tipo de datos T. Lo que significa que no puede mover el puntero, pero puede cambiar el valor señalado por el puntero. es decir *ptr2 = 19
, funcionará pero ptr2++ ; ptr2--
etc. no funcionará. Leer al revés: puntero constante a un tipo T
const T * const ptr3 ;
Un puntero constante a un tipo de datos constante T. Lo que significa que no puede mover el puntero ni cambiar el puntero del tipo de datos para que sea el puntero. es decir. ptr3-- ; ptr3++ ; *ptr3 = 19;
no trabajará
3) Punteros inteligentes : { #include <memory>
}
Puntero compartido :
T a ;
//shared_ptr<T> shptr(new T) ; not recommended but works
shared_ptr<T> shptr = make_shared<T>(); // faster + exception safe
std::cout << shptr.use_count() ; // 1 // gives the number of "
things " pointing to it.
T * temp = shptr.get(); // gives a pointer to object
// shared_pointer used like a regular pointer to call member functions
shptr->memFn();
(*shptr).memFn();
//
shptr.reset() ; // frees the object pointed to be the ptr
shptr = nullptr ; // frees the object
shptr = make_shared<T>() ; // frees the original object and points to new object
Implementado utilizando el recuento de referencias para realizar un seguimiento de cuántas "cosas" apuntan al objeto señalado por el puntero. Cuando este recuento llega a 0, el objeto se elimina automáticamente, es decir, se elimina el objeto cuando todo el share_ptr que apunta al objeto queda fuera de alcance. Esto elimina el dolor de cabeza de tener que eliminar objetos que ha asignado usando new.
Puntero débil: ayuda a lidiar con la referencia cíclica que surge cuando se usa el puntero compartido. se eliminará cuando los punteros compartidos estén fuera de alcance. Para resolver esto, cambie el miembro interno de shared_ptr a weak_ptr. Nota: Para acceder al elemento señalado por un puntero débil, use lock (), esto devuelve un débil_ptr.
T a ;
shared_ptr<T> shr = make_shared<T>() ;
weak_ptr<T> wk = shr ; // initialize a weak_ptr from a shared_ptr
wk.lock()->memFn() ; // use lock to get a shared_ptr
// ^^^ Can lead to exception if the shared ptr has gone out of scope
if(!wk.expired()) wk.lock()->memFn() ;
// Check if shared ptr has gone out of scope before access
Ver: ¿ Cuándo es útil std :: weak_ptr?
Puntero único: puntero inteligente liviano con propiedad exclusiva. Se usa cuando el puntero apunta a objetos únicos sin compartir los objetos entre los punteros.
unique_ptr<T> uptr(new T);
uptr->memFn();
//T * ptr = uptr.release(); // uptr becomes null and object is pointed to by ptr
uptr.reset() ; // deletes the object pointed to by uptr
Para cambiar el objeto señalado por el ptr único, use la semántica de movimiento
unique_ptr<T> uptr1(new T);
unique_ptr<T> uptr2(new T);
uptr2 = std::move(uptr1);
// object pointed by uptr2 is deleted and
// object pointed by uptr1 is pointed to by uptr2
// uptr1 becomes null
Referencias: Pueden considerarse esencialmente como punteros constantes, es decir, un puntero que es constante y no se puede mover con una mejor sintaxis.
Ver: ¿Cuáles son las diferencias entre una variable de puntero y una variable de referencia en C ++?
r-value reference : reference to a temporary object
l-value reference : reference to an object whose address can be obtained
const reference : reference to a data type which is const and cannot be modified
Referencia: https://www.youtube.com/channel/UCEOGtxYTB6vo6MQ-WQ9W_nQ Gracias a Andre por señalar esta pregunta.
Un puntero inteligente es una clase, un contenedor de un puntero normal. A diferencia de los punteros normales, el círculo de vida del punto inteligente se basa en un recuento de referencia (cuántas veces se asigna el objeto puntero inteligente). Entonces, cada vez que se asigna un puntero inteligente a otro, el recuento de referencia interna más más. Y cada vez que el objeto se sale del alcance, la referencia cuenta menos menos.
El puntero automático, aunque parece similar, es totalmente diferente del puntero inteligente. Es una clase conveniente que desasigna el recurso cada vez que un objeto puntero automático sale del alcance variable. Hasta cierto punto, hace que un puntero (a la memoria asignada dinámicamente) funcione de manera similar a una variable de pila (asignada estáticamente en tiempo de compilación).
Los punteros inteligentes son aquellos en los que no tiene que preocuparse por la desasignación de memoria, el intercambio de recursos y la transferencia.
Puede usar estos punteros de la misma manera que cualquier asignación funciona en Java. En Java Garbage Collector hace el truco, mientras que en Smart Pointers, el truco lo hacen los Destructores.
Las respuestas existentes son buenas, pero no cubren qué hacer cuando un puntero inteligente no es la respuesta (completa) al problema que está tratando de resolver.
Entre otras cosas (explicado bien en otras respuestas), usar un puntero inteligente es una posible solución para ¿Cómo usamos una clase abstracta como un tipo de retorno de función? que ha sido marcado como un duplicado de esta pregunta. Sin embargo, la primera pregunta que debe hacerse si está tentado a especificar una clase base abstracta (o de hecho, cualquiera) como tipo de retorno en C ++ es "¿qué quiere decir realmente?". Hay una buena discusión (con más referencias) de la programación idiomática orientada a objetos en C ++ (y cómo esto es diferente a otros lenguajes) en la documentación de la biblioteca de contenedor de puntero de impulso. En resumen, en C ++ tienes que pensar en la propiedad. Con qué punteros inteligentes le ayudan, pero no son la única solución, o siempre son una solución completa (no le dan una copia polimórfica) y no siempre son una solución que desea exponer en su interfaz (y un retorno de función suena horrible mucho como una interfaz). Puede ser suficiente devolver una referencia, por ejemplo. Pero en todos estos casos (puntero inteligente, contenedor de puntero o simplemente devolver una referencia) ha cambiado el retorno de un valor a alguna forma de referencia . Si realmente necesita una copia, es posible que deba agregar más "modismos" repetitivos o ir más allá de la OOP idiomática (o de otro modo) en C ++ a un polimorfismo más genérico utilizando bibliotecas como Adobe Poly o Boost..