shared_ptr - smart pointer c++
¿Qué es un puntero inteligente y cuándo debo usar uno? (14)
¿Qué es un puntero inteligente y cuándo debo usar uno?
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 plantilla 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 inicialicen como punteros nulos o punteros a un objeto de montón. Se comprueba la dirección a través de un puntero nulo. Nunca es necesario eliminar. Los objetos se liberan automáticamente cuando el último puntero hacia ellos ha desaparecido. 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);
Aquí hay una respuesta simple para estos días de C ++ moderno:
- ¿Qué es un puntero inteligente?
Es un tipo cuyos valores se pueden usar como punteros, pero que proporciona la característica adicional de la administración automática de memoria: cuando ya no se usa un puntero inteligente, la memoria a la que apunta está desasignada (consulte también la definición más detallada en Wikipedia ). - ¿Cuándo debo usar uno?
En el código que implica el seguimiento de la propiedad de una parte de la memoria, asignar o desasignar; El puntero inteligente a menudo le ahorra la necesidad de hacer estas cosas explícitamente. - Pero, ¿qué puntero inteligente debo usar en cuál de esos casos?
- Use
std::unique_ptr
cuando no pretenda mantener varias referencias al mismo objeto. Por ejemplo, utilícelo para un puntero a la memoria que se asigna al ingresar cierto alcance y se desasigna al salir del alcance. - Use
std::shared_ptr
cuando quiera referirse a su objeto desde múltiples lugares, y no desea que se lo desasigne hasta que todas estas referencias hayan desaparecido. - Use
std::weak_ptr
cuando quiera referirse a su objeto desde múltiples lugares, para aquellas referencias para las que está bien ignorar y desasignar (de modo que solo notarán que el objeto desapareció cuando intente desreferencia). - No utilice los indicadores de
boost::
smart ostd::auto_ptr
excepto en casos especiales que puede leer si es necesario.
- Use
- ¡Oye, no pregunté cuál usar!
Ah, pero realmente querías, admitelo. - Entonces, ¿cuándo debo usar punteros regulares entonces?
Principalmente en código que es ajeno a la propiedad de la memoria. Por lo general, esto sería en funciones que obtienen un puntero de otro lugar y no asignan, anulan la asignación o almacenan una copia del puntero que supera su ejecución.
Deje que T sea una clase en este tutorial Los punteros en C ++ se pueden dividir en 3 tipos:
1) Punteros en bruto :
T a;
T * _ptr = &a;
Mantienen una dirección de memoria a una ubicación en la memoria. Utilícelo con precaución, ya que los programas se vuelven difíciles de mantener.
Punteros con datos const o dirección {Leer al revés}
T a ;
const T * ptr1 = &a ;
T const * ptr1 = &a ;
Puntero a un tipo de datos T que es una const. Lo que significa que no puede cambiar el tipo de datos utilizando el puntero. es decir *ptr1 = 19
; no trabajará. Pero puedes mover el puntero. es decir, ptr1++ , ptr1--
; etc funcionará. Leer al revés: puntero al tipo T que es const
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 apuntado por el puntero. es decir, *ptr2 = 19
funcionará pero ptr2++ ; ptr2--
ptr2++ ; ptr2--
etc no funcionará. Leer al revés: puntero const a un tipo T
const T * const ptr3 ;
Un puntero const a un tipo de datos const T. Lo que significa que no puede mover el puntero ni puede 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 usando el conteo de referencias para hacer un seguimiento de la cantidad de "cosas" que apuntan al objeto apuntado por el puntero. Cuando esta cuenta llega a 0, el objeto se elimina automáticamente, es decir, el objeto se elimina cuando todo el share_ptr que apunta al objeto queda fuera del alcance. Esto elimina el dolor de cabeza de tener que eliminar objetos que ha asignado utilizando nuevos.
Puntero débil: ayuda a lidiar con la referencia cíclica que surge cuando se usa el puntero compartido. Si tiene dos objetos apuntados por dos punteros compartidos y hay un puntero compartido interno que apunta a los otros punteros compartidos, entonces habrá una referencia cíclica y el objeto no Se eliminará cuando los punteros compartidos queden fuera del alcance. Para resolver esto, cambie el miembro interno de shared_ptr a weak_ptr. Nota: Para acceder al elemento al que apunta un puntero débil use lock (), esto devuelve un valor 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 de peso ligero con propiedad exclusiva. Se utiliza 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 al que apunta 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: se pueden considerar esencialmente como punteros const, es decir, un puntero que es constante y no se puede mover con una mejor sintaxis.
Consulte: ¿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.
La mayoría de los tipos de punteros inteligentes manejan la eliminación del puntero a objeto por usted. Es muy útil porque ya no tiene que pensar en deshacerse de los objetos manualmente.
Los punteros inteligentes más comúnmente usados son std::tr1::shared_ptr
(o boost::shared_ptr
), y, menos comúnmente, 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 los que los objetos deben "pasar a través de los límites de DLL" (el caso de pesadilla común si se utilizan diferentes libc
s entre su código y los DLL).
Las definiciones proporcionadas por Chris, Sergdev y Llyod son correctas. Sin embargo, prefiero una definición más simple, solo para mantener mi vida simple: un puntero inteligente es simplemente una clase que sobrecarga a los operadores ->
y *
. Lo que significa que su objeto semánticamente parece un puntero, pero puede hacerlo hacer cosas más shared_ptr
, incluyendo el conteo 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.
Las respuestas existentes son buenas, pero no cubren qué hacer cuando un puntero inteligente no es la respuesta (completa) al problema que intenta resolver.
Entre otras cosas (explicadas bien en otras respuestas), usar un puntero inteligente es una posible solución a ¿Cómo utilizamos 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?". Existe una buena discusión (con referencias adicionales) de la programación orientada a objetos idiomática en C ++ (y en qué se diferencia de otros idiomas) en la documentación de la biblioteca de contenedores de pointer boost . En resumen, en C ++ hay que pensar en la propiedad. Los 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 el retorno de una función suena horrible) mucho como una interfaz). Podría ser suficiente para devolver una referencia, por ejemplo. Pero en todos estos casos (puntero inteligente, contenedor de puntero o simplemente devolviendo 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 "lenguaje idiomático" o ir más allá de la POO idiomática (o de otro tipo) en C ++ a un polimorfismo más genérico utilizando bibliotecas como Adobe Poly o Boost.TypeErasure .
Los punteros inteligentes son aquellos en los que no tiene que preocuparse por la Desasignación de memoria, el Uso compartido de recursos y la Transferencia.
Puede utilizar muy bien este puntero 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.
Me gustaría agregar un punto más a la pregunta anterior, smart pointer std :: shared_ptr no tiene operador de subíndices y no es compatible con la aritmética de ponter, podemos usar get () para obtener un puntero incorporado.
Un puntero inteligente es como un puntero normal (escrito), como "char *", excepto cuando el puntero en sí queda fuera del alcance, entonces lo que apunta también se elimina. Puede usarlo como si fuera un puntero normal, usando "->", pero no si necesita un puntero real a los datos. Para eso, puedes 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 datos de las clases, 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:
- ... el puntero no debería ser el propietario de los datos ... es decir, cuando solo está utilizando los datos, pero desea que sobrevivan a la función a la que hace referencia.
- ... el puntero inteligente no va a ser destruido en algún momento. No desea que se quede en la memoria que nunca se destruye (como en un objeto que se asigna dinámicamente pero no se eliminará explícitamente).
- ... dos punteros inteligentes podrían apuntar a los mismos datos. (Hay, sin embargo, incluso punteros más inteligentes que manejarán eso ... eso se llama conteo de referencias ).
Ver también:
- recogida de basura .
- Esta pregunta de desbordamiento de pila con respecto a la propiedad de datos
Un puntero inteligente es un objeto que actúa como un puntero, pero además proporciona control sobre la construcción, destrucción, copia, movimiento y eliminación de referencias.
Uno puede implementar su propio puntero inteligente, pero muchas bibliotecas también proporcionan implementaciones de puntero inteligente cada una con diferentes ventajas e inconvenientes.
Por ejemplo, Boost proporciona las siguientes implementaciones de punteros inteligentes:
-
shared_ptr<T>
es un puntero aT
usa 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 queda fuera del alcance. Ninguna asignación es posible. -
intrusive_ptr<T>
es otro puntero de conteo de referencia. Proporciona un mejor rendimiento queshared_ptr
, pero requiere que el tipoT
proporcione su propio mecanismo de conteo de referencias. -
weak_ptr<T>
es un puntero débil, que trabaja en conjunto conshared_ptr
para evitar referencias circulares. -
shared_array<T>
es comoshared_ptr
, pero para matrices deT
-
scoped_array<T>
es comoscoped_ptr
, pero para matrices deT
Estas son solo una descripciones lineales de cada una y se pueden usar según la necesidad; para más detalles y ejemplos, se 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.
Un puntero inteligente es una clase que envuelve un puntero de C ++ "sin procesar" (o "desnudo"), para administrar la vida útil del objeto al que se apunta. No hay un solo tipo de puntero inteligente, pero todos intentan abstraer un puntero en bruto de una manera práctica.
Los punteros inteligentes deben ser preferidos a los punteros en bruto. Si siente que necesita usar punteros (primero considere si realmente lo hace), normalmente querría usar un puntero inteligente, ya que esto puede aliviar muchos de los problemas con los punteros sin procesar, olvidándose principalmente de eliminar el objeto y la pérdida de memoria.
Con los 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 por comparación define una política de cuándo se destruye el objeto. Todavía tienes que crear el objeto, pero ya no tienes que preocuparte 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 envoltorio del puntero inteligente, como implementado por boost::scoped_ptr
o std::unique_ptr
.
void f()
{
{
boost::scoped_ptr<MyObject> ptr(new MyObject());
ptr->DoSomethingUseful();
} // boost::scopted_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 scoped_ptr
instancias de scoped_ptr
no se pueden copiar. Esto evita que el puntero se elimine varias veces (incorrectamente). Sin embargo, puede pasar referencias a otras funciones que llame.
Los punteros de ámbito son útiles cuando desea vincular la vida útil del objeto a un bloque de código en particular, o si lo integró como datos miembro dentro de otro objeto, la vida útil de ese otro objeto. El objeto existe hasta que se sale del bloque de código que contiene, o hasta que el objeto que contiene se destruye.
Una política de puntero inteligente más compleja implica referencia al conteo del puntero. Esto permite copiar el puntero. Cuando se destruye la última "referencia" al objeto, el objeto se elimina. Esta política se implementa mediante 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 u otro objeto.
Hay un inconveniente para hacer referencia a los 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 {
boost::shared_ptr<Owner> other;
};
boost::shared_ptr<Owner> p1 (new Owner());
boost::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 solucionar este problema, tanto Boost como C ++ 11 han definido un weak_ptr
para definir una referencia débil (sin shared_ptr
) a un shared_ptr
.
ACTUALIZAR
Esta respuesta es bastante antigua, por lo que describe lo que era "bueno" en ese momento, que era punteros inteligentes proporcionados por la biblioteca Boost. Desde C ++ 11, la biblioteca estándar ha proporcionado suficientes tipos de punteros inteligentes, por lo que debería favorecer el uso de std::unique_ptr
, std::shared_ptr
y std::weak_ptr
.
También está std::auto_ptr
. Es muy parecido a un puntero de ámbito, excepto que también tiene la capacidad peligrosa "especial" para copiarse, ¡lo que también se transfiere inesperadamente a la propiedad! Está en desuso en los estándares más nuevos, por lo que no debe usarlo. Utilice el std::unique_ptr
en std::unique_ptr
lugar.
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.
Un puntero inteligente es una clase, una envoltura 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 (la cantidad de tiempo que se asigna el objeto puntero inteligente). Entonces, cuando se asigna un puntero inteligente a otro, el recuento de referencias internas plus plus. Y cada vez que el objeto queda fuera del alcance, el recuento de referencias menos menos.
El puntero automático, aunque parece similar, es totalmente diferente del puntero inteligente. Es una clase conveniente que desasigna el recurso cuando un objeto de puntero automático sale del ámbito 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).
El puntero inteligente es un tipo de puntero con alguna funcionalidad adicional, por ejemplo, desasignación automática de memoria, conteo de referencias, etc.
La introducción pequeña está disponible en la página Punteros inteligentes: ¿qué, por qué, 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 la memoria automáticamente cuando está fuera de alcance y que es más robusto que el uso de punteros simples cuando se lanzan excepciones, aunque menos flexible.
Otro tipo conveniente es boost::shared_ptr
que implementa el conteo de referencias y desasigna automáticamente la memoria cuando no queda ninguna referencia 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 de C ++: La guía completa" de David Vandevoorde, Nicolai M. Josuttis , capítulo Capítulo 20. Punteros inteligentes. Algunos temas tratados:
- Protección contra excepciones
- Tenedores, (nota, std::auto_ptr es implementación de tal tipo de puntero inteligente)
- La adquisición de recursos es la inicialización (esto se usa con frecuencia para la administración de recursos segura en Excepciones en C ++)
- Limitaciones del titular
- Recuento de referencias
- Acceso simultáneo al mostrador
- Destrucción y desasignación
http://en.wikipedia.org/wiki/Smart_pointer
En informática, un puntero inteligente es un tipo de datos abstracto que simula un puntero al tiempo que proporciona funciones 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 y al mismo tiempo mantener la eficiencia. Los punteros inteligentes normalmente 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 se produzcan algunas pérdidas de memoria. Los punteros inteligentes intentan evitar las fugas de memoria haciendo que la asignación de recursos sea automática: cuando el puntero a un objeto (o el último de una serie de punteros) se destruye, por ejemplo, porque se sale del alcance, el objeto puntiagudo también se destruye.