¿Qué es un puntero inteligente y cuándo debería usar uno?

¿Qué es un puntero inteligente y cuándo debería usar uno?

    Un puntero inteligente es una clase que envuelve un puntero C ++ “en bruto” (o “simple”) 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 sin procesar de una manera práctica.

    Los punteros inteligentes deberían preferirse a los punteros sin formato. Si cree que necesita utilizar punteros (primero considere si realmente lo hace), normalmente debería utilizar un puntero inteligente, ya que esto puede aliviar muchos de los problemas con los punteros sin procesar, principalmente olvidándose de eliminar el objeto y la fuga de memoria.

    Con punteros sin formato, el progtwigdor 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. Aún debe crear el objeto, pero ya no tiene que preocuparse por destruirlo.

     SomeSmartPtr 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 scope del objeto contenedor de puntero inteligente, como implementado por boost::scoped_ptr o std::unique_ptr .

     void f() { { boost::scoped_ptr 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 scoped_ptr no se pueden copiar. Esto evita que el puntero se elimine varias veces (incorrectamente). Sin embargo, puede pasarle referencias a otras funciones que llame.

    Los indicadores de ámbito son útiles cuando desea vincular la vida útil del objeto a un bloque de código en particular, o si lo incrustó como datos de miembro dentro de otro objeto, la vida útil de ese otro objeto. El objeto existe hasta que se abandona el bloque de código que lo contiene, o hasta que el objeto que lo contiene se destruya.

    Una política de puntero inteligente más compleja implica referencia contando el puntero. Esto permite copiar el puntero. Cuando se destruye la última “referencia” al objeto, el objeto se elimina. Esta política está implementada por boost::shared_ptr y std::shared_ptr .

     void f() { typedef std::shared_ptr 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 del código oa otro objeto.

    Hay un inconveniente para hacer referencia a los apuntadores 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 other; }; boost::shared_ptr p1 (new Owner()); boost::shared_ptr 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, Boost y C ++ 11 han definido un weak_ptr para definir una referencia débil (sin contar) a un shared_ptr .


    ACTUALIZAR

    Esta respuesta es bastante antigua y, por lo tanto, describe lo que era ‘bueno’ en ese momento, que eran punteros inteligentes proporcionados por la biblioteca de Boost. Desde C ++ 11, la biblioteca estándar ha proporcionado suficientes tipos de punteros inteligentes, por lo que debe favorecer el uso de std::unique_ptr , std::shared_ptr y std::weak_ptr .

    También hay 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 transfiere inesperadamente la propiedad. Está obsoleto en los estándares más nuevos, por lo que no debe usarlo. Use std::unique_ptr en std::unique_ptr lugar.

     std::auto_ptr p1 (new MyObject()); std::auto_ptr p2 = p1; // Copy and transfer ownership. // p1 gets set to empty! p2->DoSomething(); // Works. p1->DoSomething(); // Oh oh. Hopefully raises some NULL pointer exception. 

    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 proporciona la función adicional de administración de memoria automática: cuando un puntero inteligente ya no está en uso, la memoria a la que apunta es desasignada (consulte también la definición más detallada en Wikipedia ).
    • ¿Cuándo debería usar uno?
      En el código que implica el seguimiento de la propiedad de una pieza de memoria, la asignación o la desasignación; el puntero inteligente a menudo le ahorra la necesidad de hacer estas cosas de forma explícita.
    • Pero, ¿qué puntero inteligente debería usar en cuál de esos casos?
      • Use std::unique_ptr cuando no tenga la intención de mantener múltiples referencias al mismo objeto. Por ejemplo, utilícelo como un puntero a la memoria que se asigna al ingresar algún ámbito y se desasigna al salir del scope.
      • Use std::shared_ptr cuando quiera referirse a su objeto desde múltiples lugares, y no quiere que se desasigne hasta que todas estas referencias hayan desaparecido.
      • Utilice std::weak_ptr cuando quiera referirse a su objeto desde múltiples lugares, para aquellas referencias que está bien ignorar y desasignar (de modo que simplemente notarán que el objeto se ha ido cuando intenta desreferencia).
      • No utilice boost:: smart puninters o std::auto_ptr excepto en casos especiales que puede leer si es necesario.
    • ¡Oye, no pregunté cuál usar!
      Ah, pero realmente querías, admítelo.
    • Entonces, ¿cuándo debería usar punteros regulares?
      Principalmente en código que es ajeno a la propiedad de la memoria. Esto normalmente sería en funciones que obtienen un puntero desde otro lugar y no asignan, desasignan ni almacenan una copia del puntero que dura más que su ejecución.

    El puntero inteligente es un tipo de puntero con alguna funcionalidad adicional, por ejemplo, desasignación de memoria automática, recuento 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 de puntero inteligente simple es std::auto_ptr (capítulo 20.4.5 del estándar de C ++), que permite desasignar la memoria automáticamente cuando está fuera del scope y que es más sólida que el uso del puntero simple cuando se lanzan excepciones, aunque menos flexible.

    Otro tipo conveniente es 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 memory leaks y es fácil de usar para implementar RAII .

    El tema está cubierto en profundidad en el libro “Plantillas en C ++: La Guía Completa” de David Vandevoorde, Nicolai M. Josuttis , capítulo Capítulo 20. Punteros inteligentes. Algunos temas cubiertos:

    • Protegiendo Contra Excepciones
    • Titulares, (nota, std :: auto_ptr es la implementación de dicho 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 para excepciones en C ++)
    • Limitaciones del titular
    • Recuento de referencia
    • Acceso simultáneo a mostrador
    • Destrucción y desasignación

    Las definiciones proporcionadas por Chris, Sergdev y Llyod son correctas. Sin embargo, prefiero una definición más simple, simplemente para mantener mi vida simple: Un puntero inteligente es simplemente una clase que sobrecarga los operadores -> y * . Lo que significa que su objeto semánticamente parece un puntero, pero puede hacerlo de una forma más fría, como 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 (typescript), como “char *”, excepto cuando el puntero sale del scope y luego se borra también lo que apunta. Puede usarlo como lo haría con un puntero normal, usando “->”, pero no si necesita un puntero real a los datos. Para eso, puedes usar “& * ptr”.

    Es útil para:

    • Objetos que se deben asignar con nuevos, pero que le gustaría tener la misma duración que algo en esa stack. Si el objeto está asignado a un puntero inteligente, se eliminarán cuando el progtwig salga de esa función / bloque.

    • Los miembros de datos de las clases, de modo que cuando se elimina el objeto, todos los datos de propiedad también se eliminan, sin ningún código especial en el destructor (deberá asegurarse de que el destructor sea virtual, lo cual es casi siempre una buena acción) .

    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 quiere que sobrevivan a la función en la que lo está haciendo referencia.
    • … el puntero inteligente no va a ser destruido en algún momento. No desea que se guarde en la memoria que nunca se destruya (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. (Sin embargo, hay punteros más inteligentes que manejarán eso … eso se llama conteo de referencia ).

    Ver también:

    • recolección de basura .
    • Esta pregunta de desbordamiento de stack con respecto a la propiedad de datos

    La mayoría de los tipos de punteros inteligentes manejan la eliminación del objeto de 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 utilizados son std::tr1::shared_ptr (o boost::shared_ptr ) y, con menos frecuencia, std::auto_ptr . Recomiendo el uso regular de shared_ptr .

    shared_ptr es muy versátil y trata con una gran variedad de escenarios de eliminación, incluidos los casos en que los objetos deben “traspasarse los límites de la DLL” (el caso de pesadilla común si se utilizan diferentes libc 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, la destrucción, la copia, el movimiento y la desreferenciación.

    Uno puede implementar su propio puntero inteligente, pero muchas bibliotecas también proporcionan implementaciones de punteros inteligentes, cada una con diferentes ventajas e inconvenientes.

    Por ejemplo, Boost proporciona las siguientes implementaciones de puntero inteligente:

    • shared_ptr es un puntero a T usa un recuento de referencia para determinar cuándo el objeto ya no es necesario.
    • scoped_ptr es un puntero que se borra automáticamente cuando sale del scope. Ninguna asignación es posible.
    • intrusive_ptr 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 recuento de referencias.
    • weak_ptr es un puntero débil, que trabaja en conjunto con shared_ptr para evitar referencias circulares.
    • shared_array es como shared_ptr , pero para matrices de T
    • scoped_array 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.

    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 a los operadores de puntero estándar. Tienen una serie de ventajas sobre los punteros regulares. Están garantizados para ser inicializados como punteros nulos o punteros a un objeto de montón. Se verifica la dirección a través de un puntero nulo. No eliminar nunca es necesario. Los objetos se liberan automáticamente cuando el último puntero a ellos se ha ido. Un problema importante con estos indicadores inteligentes es que, a diferencia de los indicadores habituales, no respetan la herencia. Los punteros inteligentes no son atractivos para el código polimórfico. A continuación se incluye un ejemplo para la implementación de punteros inteligentes.

    Ejemplo:

     template  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  &); const smart_pointer  & operator =(const smart_pointer&); ~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í es cómo usarlo:

     smart_pointer  p= employee("Harris",1333); 

    Al igual que otros operadores sobrecargados, p se comportará como un puntero regular,

     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 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 uso indebido de punteros a la vez que conservan la eficacia. Los punteros inteligentes suelen realizar un seguimiento de los objetos que los señalan con el fin de gestionar la memoria. El uso indebido de punteros es una fuente importante de errores: la asignación constante, desasignación y referencia que debe realizar un progtwig escrito utilizando punteros hace que sea muy probable que se produzcan algunas pérdidas de memoria. Los punteros inteligentes intentan evitar memory leaks al hacer que la asignación de recurso sea automática: cuando se destruye el puntero a un objeto (o el último de una serie de punteros), por ejemplo, porque se sale del scope, el objeto puntiagudo también se destruye.

    Deje que T sea una clase en este tutorial. Los punteros en C ++ se pueden dividir en 3 tipos:

    1) Punteros brutos :

     T a; T * _ptr = &a; 

    Mantienen una dirección de memoria en una ubicación en la memoria. Use con precaución, ya que los progtwigs se vuelven complejos y difíciles de controlar.

    Punteros con información o dirección const {Lectura 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 con el puntero. es decir, *ptr1 = 19 ; no trabajará. Pero puedes mover el puntero. es decir, ptr1++ , ptr1-- ; etc funcionará. Lee al revés: apunta al tipo T que es const

      T * const ptr2 ; 

    Un puntero const 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á. Lee al revés: const pointer 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 cambiar el puntero del tipo de datos para que sea el puntero. es decir. ptr3-- ; ptr3++ ; *ptr3 = 19; no trabajará

    3) Punteros inteligentes : { #include }

    Puntero compartido :

      T a ; //shared_ptr shptr(new T) ; not recommended but works shared_ptr shptr = make_shared(); // 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() ; // 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 al que apunta el puntero. Cuando este recuento va a 0, el objeto se elimina automáticamente, es decir, se objeta se elimina cuando todo el share_ptr que apunta al objeto queda fuera del scope. Esto elimina el dolor de cabeza de tener que eliminar los objetos que ha asignado usando new.

    Weak Pointer: ayuda a lidiar con la referencia cíclica que surge cuando se utiliza el puntero compartido. Si tiene dos objetos apuntados por dos punteros compartidos y hay un puntero compartido interno apuntando al puntero compartido de los demás, habrá una referencia cíclica y el objeto no se eliminará cuando los punteros compartidos estén fuera del scope. Para solucionar esto, cambie el miembro interno de un shared_ptr a weak_ptr. Nota: Para acceder al elemento apuntado por un puntero débil use lock (), esto devuelve un weak_ptr.

     T a ; shared_ptr shr = make_shared() ; weak_ptr 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 std :: weak_ptr útil?

    Puntero único: puntero inteligente ligero con propiedad exclusiva. Se usa cuando el puntero apunta a objetos únicos sin compartir los objetos entre los punteros.

     unique_ptr 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 uptr1(new T); unique_ptr 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 usar como punteros const, es decir, un puntero que es const y no se puede mover con una mejor syntax.

    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 ciclo de vida del punto inteligente se basa en un recuento de referencias (el tiempo que se le asigna al objeto del puntero inteligente). Entonces, cada vez que se asigna un puntero inteligente a otro, el recuento de referencia interno más más. Y cada vez que el objeto sale del scope, el recuento de referencia 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 ámbito variable. Hasta cierto punto, hace que un puntero (a memoria asignada dinámicamente) funcione de manera similar a una variable de stack (asignada estáticamente en tiempo de comstackción).

    Los Smart Pointers 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 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 realizan los Destructors.

    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 (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 tiene la tentación de especificar una clase base abstracta (o de hecho, cualquier) como un tipo de retorno en C ++ es “¿qué quiere decir realmente?”. Hay una buena discusión (con más referencias) de la progtwigción orientada a objetos idiomáticos en C ++ (y cómo esto es diferente a otros lenguajes) en la documentación de la biblioteca de contenedor de punteros boost . En resumen, en C ++ tienes que pensar en la propiedad. Con qué punteros inteligentes lo ayudan, pero no son la única solución, o siempre 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 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 copiar, es posible que necesite agregar más expresiones idiomáticas estándar o ir más allá de la OOP idiomática (o de otro modo) en C ++ para obtener un polymorphism más genérico utilizando bibliotecas como Adobe Poly o Boost.TypeErasure .

    Me gustaría agregar un punto más a la pregunta anterior, el puntero inteligente std :: shared_ptr no tiene operador de subíndice y no admite la aritmética de ponter, podemos usar get () para obtener un puntero incorporado.