¿Cuáles son los peligros potenciales al usar boost:: shared ptr?


¿Cuáles son algunas formas en que puede dispararse en el pie al usar boost::shared_ptr? En otras palabras, ¿qué trampas tengo que evitar cuando uso boost::shared_ptr?

Author: ThiefMaster, 2009-03-31

13 answers

Referencias cíclicas: a shared_ptr<> a algo que tiene un shared_ptr<> al objeto original. Puedes usar weak_ptr<> para romper este ciclo, por supuesto.


Añado lo siguiente como ejemplo de lo que estoy hablando en los comentarios.

class node : public enable_shared_from_this<node> {
public :
    void set_parent(shared_ptr<node> parent) { parent_ = parent; }
    void add_child(shared_ptr<node> child) {
        children_.push_back(child);
        child->set_parent(shared_from_this());
    }

    void frob() {
        do_frob();
        if (parent_) parent_->frob();
    }

private :
    void do_frob();
    shared_ptr<node> parent_;
    vector< shared_ptr<node> > children_;
};

En este ejemplo, tiene un árbol de nodos, cada uno de los cuales contiene un puntero a su padre. La función miembro frob (), por cualquier razón, se ondula hacia arriba a través del árbol. (Esto no es del todo extravagante; algunos frameworks GUI funcionan así manera).

El problema es que, si pierde la referencia al nodo superior, entonces el nodo superior todavía tiene fuertes referencias a sus hijos, y todos sus hijos también tienen una fuerte referencia a sus padres. Esto significa que hay referencias circulares que evitan que todas las instancias se limpien por sí mismas, mientras que no hay forma de llegar al árbol desde el código, esta memoria se filtra.

class node : public enable_shared_from_this<node> {
public :
    void set_parent(shared_ptr<node> parent) { parent_ = parent; }
    void add_child(shared_ptr<node> child) {
        children_.push_back(child);
        child->set_parent(shared_from_this());
    }

    void frob() {
        do_frob();
        shared_ptr<node> parent = parent_.lock(); // Note: parent_.lock()
        if (parent) parent->frob();
    }

private :
    void do_frob();
    weak_ptr<node> parent_; // Note: now a weak_ptr<>
    vector< shared_ptr<node> > children_;
};

Aquí, el nodo padre ha sido reemplazado por un puntero débil. Ya no tiene voz y voto en la vida del nodo al que se refiere. Por lo tanto, si el nodo superior sale del ámbito como en el ejemplo anterior, entonces mientras tiene fuertes referencias a sus hijos, sus hijos no tienen fuertes referencias a sus padres. Por lo tanto, no hay referencias fuertes al objeto, y se limpia a sí mismo. A su vez, esto hace que los niños pierdan su única referencia fuerte, lo que hace que limpien, y así sucesivamente. En resumen, esto no se filtrará. Y sólo por estratégicamente reemplazar un shared_ptr por un weak_ptr.

Nota: lo anterior se aplica igualmente a std::shared_ptr y std::weak_ptr, como a boost::shared_ptr y boost::weak_ptr.

 41
Author: Kaz Dragon,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-03-30 09:17:16

Creando múltiples shared_ptr's no relacionados al mismo objeto:

#include <stdio.h>
#include "boost/shared_ptr.hpp"

class foo
{
public:
    foo() { printf( "foo()\n"); }

    ~foo() { printf( "~foo()\n"); }
};

typedef boost::shared_ptr<foo> pFoo_t;

void doSomething( pFoo_t p)
{
    printf( "doing something...\n");
}

void doSomethingElse( pFoo_t p)
{
    printf( "doing something else...\n");
}

int main() {
    foo* pFoo = new foo;

    doSomething( pFoo_t( pFoo));
    doSomethingElse( pFoo_t( pFoo));

    return 0;
}
 24
Author: Michael Burr,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-03-31 15:28:03

Construir un puntero compartido temporal anónimo, por ejemplo dentro de los argumentos de una llamada a una función:

f(shared_ptr<Foo>(new Foo()), g());

Esto se debe a que es permisible que el new Foo() sea ejecutado, luego g() llamado, y g() lanzar una excepción, sin que el shared_ptr nunca se configure, por lo que el shared_ptr no tiene la oportunidad de limpiar el objeto Foo.

 18
Author: Brian Campbell,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-04-04 00:33:31

Tenga cuidado haciendo dos punteros al mismo objeto.

boost::shared_ptr<Base> b( new Derived() );
{
  boost::shared_ptr<Derived> d( b.get() );
} // d goes out of scope here, deletes pointer

b->doSomething(); // crashes

En su lugar use esto

boost::shared_ptr<Base> b( new Derived() );
{
  boost::shared_ptr<Derived> d = 
    boost::dynamic_pointer_cast<Derived,Base>( b );
} // d goes out of scope here, refcount--

b->doSomething(); // no crash

Además, cualquier clase que contenga shared_ptrs debe definir constructores de copia y operadores de asignación.

No intentes usar shared_from_this() en el constructor't no funcionará. En su lugar, cree un método estático para crear la clase y haga que devuelva un shared_ptr.

He pasado referencias a shared_ptrs sin problemas. Solo asegúrese de que se copie antes de guardarlo (es decir, no referencias como miembros de la clase).

 13
Author: ,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-04-03 23:47:20

Aquí hay dos cosas a evitar:

  • Llamar a la función get() para obtener el puntero raw y usarlo después de que el objeto apuntado salga del alcance.

  • Pasar una referencia o un puntero raw a un shared_ptr también debería ser peligroso, ya que no incrementará el recuento interno que ayuda a mantener vivo el objeto.

 12
Author: Frank,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-03-31 15:07:44

Depuramos varias semanas de comportamiento extraño.

La razón fue:
pasamos 'this' a algunos trabajadores de subprocesos en lugar de 'shared_from_this'.

 9
Author: Mykola Golubyev,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-03-31 15:21:09

No es precisamente una escopeta, pero sin duda una fuente de frustración hasta que te das cuenta de cómo hacerlo a la manera de C++0x: la mayoría de los predicados que conoces y amas de <functional> no juegan muy bien con shared_ptr. Felizmente, std::tr1::mem_fn funciona con objetos, punteros y shared_ptr s, reemplazando std::mem_fun, pero si desea usar std::negate, std::not1, std::plus o cualquiera de esos viejos amigos con shared_ptr, esté preparado para ponerse cómodo con std::tr1::bind y probablemente argumentar marcadores de posición también. En la práctica, esto es mucho más genérico, dado que ahora básicamente terminas usando bind para cada adaptador de objeto de función, pero toma un poco acostumbrarte si ya estás familiarizado con las funciones de conveniencia de STL.

Este artículo de DDJ toca el tema, con un montón de código de ejemplo. También blogueé sobre esto hace unos años cuando tuve que averiguar cómo hacerlo.

 4
Author: Meredith L. Patterson,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-08-07 08:53:11

Usando shared_ptr para objetos realmente pequeños (como char short) podría ser una sobrecarga si tiene muchos objetos pequeños en el montón, pero no son realmente "compartidos". boost::shared_ptr asigna 16 bytes por cada nuevo recuento de referencia que crea en g++ 4.4.3 y VS2008 con Boost 1.42. std::tr1::shared_ptr asigna 20 bytes. Ahora si usted tiene un millón distinto shared_ptr<char> que significa 20 millones de bytes de su memoria se han ido en la celebración de solo cuenta=1. Sin mencionar los costos indirectos y la fragmentación de la memoria. Pruebe con lo siguiente en tu plataforma favorita.

void * operator new (size_t size) {
  std::cout << "size = " << size << std::endl;
  void *ptr = malloc(size);
  if(!ptr) throw std::bad_alloc();
  return ptr;
}
void operator delete (void *p) {
  free(p);
}
 3
Author: Sumant,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-03-22 23:46:43

Dar un shared_ptr a esto dentro de una definición de clase también es peligroso. Utilice enabled_shared_from_this en su lugar.

Ver el siguiente post aquí

 1
Author: George Godik,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 10:30:58

Debe tener cuidado cuando use shared_ptr en código multihilo. Entonces es relativamente fácil convertirse en un caso cuando un par de shared_ptr s, apuntando a la misma memoria, es utilizado por diferentes hilos.

 1
Author: oo_olo_oo,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-04-04 10:21:43

El uso generalizado y popular de shared_ptr causará casi inevitablemente una ocupación de memoria no deseada e invisible.

Las referencias cíclicas son una causa bien conocida y algunas de ellas pueden ser indirectas y difíciles de detectar, especialmente en código complejo en el que trabaja más de un programador; un programador puede decidir que un objeto necesita una referencia a otro como solución rápida y no tiene tiempo para examinar todo el código para ver si está cerrando un ciclo. Este peligro es enorme subestimado.

Menos comprendido es el problema de las referencias inéditas. Si un objeto se comparte con muchos shared_ptrs, entonces no se destruirá hasta que cada uno de ellos se ponga a cero o salga del ámbito. Es muy fácil pasar por alto una de estas referencias y terminar con objetos ocultos en la memoria que pensabas que habías terminado.

Aunque estrictamente hablando no se trata de fugas de memoria (todo se liberará antes de que el programa salga), son tan dañino y más difícil de detectar.

Estos problemas son las consecuencias de declaraciones falsas convenientes: 1. Declarando lo que realmente quieres ser propiedad única como shared_ptr. scoped_ptr sería correcto, pero entonces cualquier otra referencia a ese objeto tendrá que ser un puntero raw, que podría dejarse colgando. 2. Declarar lo que realmente quieres que sea una referencia de observación pasiva como shared_ptr. weak_ptr sería correcto, pero entonces tienes la molestia de convertirlo a share_ptr cada vez quieres usarlo.

Sospecho que tu proyecto es un buen ejemplo del tipo de problemas en los que esta práctica puede meterte.

Si tiene una aplicación con uso intensivo de memoria, realmente necesita una propiedad única para que su diseño pueda controlar explícitamente la vida útil de los objetos.

Con una sola propiedad opObject=NULL; definitivamente eliminará el objeto y lo hará ahora.

Con propiedad compartida spObject = NULL;........quién sabe?......

 0
Author: John Morrison,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-08-07 08:21:33

Si tiene un registro de los objetos compartidos (una lista de todas las instancias activas, por ejemplo), los objetos nunca se liberarán. Solución: como en el caso de las estructuras de dependencia circulares (ver la respuesta de Kaz Dragon), use weak_ptr según corresponda.

 -1
Author: Mr Fooz,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-04-04 00:20:50

Los punteros inteligentes no son para todo, y los punteros sin procesar no se pueden eliminar

Probablemente el peor peligro es que dado que shared_ptr es una herramienta útil, la gente comenzará a ponerla en todas partes. Dado que los punteros simples pueden ser mal utilizados, las mismas personas cazarán punteros crudos e intentarán reemplazarlos con cadenas, contenedores o punteros inteligentes, incluso cuando no tenga sentido. Los usos legítimos de punteros crudos se convertirán en sospechosos. Habrá una policía de punteros.

Esto no es solo probablemente el peor peligro, puede ser el único peligro serio. Todos los peores abusos de shared_ptr serán la consecuencia directa de la idea de que los punteros inteligentes son superiores a los punteros raw (lo que sea que eso signifique), y que poner punteros inteligentes en todas partes hará que la programación en C++ sea "más segura".

Por supuesto, el mero hecho de que un puntero inteligente necesita ser convertido a un puntero raw para ser utilizado refuta esta afirmación del culto al puntero inteligente, pero el hecho de que el acceso al puntero raw es "implícito" en operator*, operator-> (o explicit in get()), pero no implícito en una conversión implícita, es suficiente para dar la impresión de que esto no es realmente una conversión, y que el puntero crudo producido por esta no conversión es un temporal inofensivo.

C++ no se puede convertir en un" lenguaje seguro", y ningún subconjunto útil de C++ es"seguro"

Por supuesto, la búsqueda de un subconjunto seguro ("seguro" en el sentido estricto de "seguro de memoria", como LISP, Haskell, Java...) de C++ está condenado a ser interminable e insatisfactorio, ya que el subconjunto seguro de C++ es pequeño y casi inútil, ya que las primitivas inseguras son la regla en lugar de la excepción. La estricta seguridad de la memoria en C++ significaría que no hay punteros y solo referencias con la clase de almacenamiento automático. Pero en un lenguaje donde el programador es de confianza por definición, algunas personas insistirán en usar algún (en principio) "puntero inteligente" a prueba de idiotas, incluso donde no hay otra ventaja sobre los punteros raw que una forma específica para atornillar el estado del programa es evitar.

 -5
Author: curiousguy,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-10-25 07:02:15