Ejemplo para usar shared_ptr?

82

Hola, hoy hice una pregunta sobre cómo insertar diferentes tipos de objetos en la misma matriz de vectores y mi código en esa pregunta fue

 gate* G[1000];
G[0] = new ANDgate() ;
G[1] = new ORgate;
//gate is a class inherited by ANDgate and ORgate classes
class gate
{
 .....
 ......
 virtual void Run()
   {   //A virtual function
   }
};
class ANDgate :public gate 
  {.....
   .......
   void Run()
   {
    //AND version of Run
   }  

};
 class ORgate :public gate 
  {.....
   .......
   void Run()
   {
    //OR version of Run
   }  

};      
//Running the simulator using overloading concept
 for(...;...;..)
 {
  G[i]->Run() ;  //will run perfectly the right Run for the right Gate type
 } 

y quería usar vectores, así que alguien escribió que debería hacer eso:

std::vector<gate*> G;
G.push_back(new ANDgate); 
G.push_back(new ORgate);
for(unsigned i=0;i<G.size();++i)
{
  G[i]->Run();
}

pero luego él y muchos otros sugirieron que sería mejor usar los contenedores de puntero Boost
o shared_ptr. He pasado las últimas 3 horas leyendo sobre este tema, pero la documentación me parece bastante avanzada. **** ¿Alguien puede darme un pequeño ejemplo de código de shared_ptruso y por qué sugirieron usar shared_ptr. También hay otros tipos como ptr_vector, ptr_listy ptr_deque** **

Edit1: también he leído un ejemplo de código que incluía:

typedef boost::shared_ptr<Foo> FooPtr;
.......
int main()
{
  std::vector<FooPtr>         foo_vector;
........
FooPtr foo_ptr( new Foo( 2 ) );
  foo_vector.push_back( foo_ptr );
...........
}

¡Y no entiendo la sintaxis!

Ahmed
fuente
2
¿Qué sintaxis no entiendes? La primera línea de maincrea un vector que puede contener punteros compartidos a un tipo llamado Foo; el segundo crea un Foouso newy un puntero compartido para administrarlo; el tercero coloca una copia del puntero compartido en el vector.
Mike Seymour

Respuestas:

116

El uso vectorde shared_ptrelimina la posibilidad de perder memoria porque se olvidó de recorrer el vector y llamar deletea cada elemento. Repasemos una versión ligeramente modificada del ejemplo línea por línea.

typedef boost::shared_ptr<gate> gate_ptr;

Cree un alias para el tipo de puntero compartido. Esto evita la fealdad en el lenguaje C ++ que resulta de escribir std::vector<boost::shared_ptr<gate> >y olvidar el espacio entre los signos de cierre mayor que .

    std::vector<gate_ptr> vec;

Crea un vector vacío de boost::shared_ptr<gate>objetos.

    gate_ptr ptr(new ANDgate);

Asigne una nueva ANDgateinstancia y guárdela en un archivo shared_ptr. La razón para hacer esto por separado es evitar un problema que puede ocurrir si se produce una operación. Esto no es posible en este ejemplo. Las "Mejores prácticas" de Boostshared_ptr explican por qué es una mejor práctica realizar la asignación en un objeto independiente en lugar de en uno temporal.

    vec.push_back(ptr);

Esto crea un nuevo puntero compartido en el vector y lo copia ptr. El recuento de referencias en las entrañas de shared_ptrasegura que el objeto asignado dentro de ptrse transfiera de forma segura al vector.

Lo que no se explica es que el destructor shared_ptr<gate>asegura que se borre la memoria asignada. Aquí es donde se evita la pérdida de memoria. El destructor for std::vector<T>asegura que Tse llama al destructor for para cada elemento almacenado en el vector. Sin embargo, el destructor de un puntero (por ejemplo, gate*) no elimina la memoria que había asignado . Eso es lo que está intentando evitar utilizando shared_ptro ptr_vector.

D.Shawley
fuente
1
Eso fue detallado :). Mi pregunta es sobre la tercera línea de código gate_ptr ptr (nuevo ANDgate); No me resulta muy familiar, ptr de un tipo de puerta de puntero compartido y luego, entre llaves, ¡envió una nueva puerta AND! Eso es confuso.
Ahmed
6
@Ahmed: la expresión general es una inicialización variable, al igual que podría escribir int x(5);para inicializar xcon el valor 5. En este caso, se inicializa con el valor de la nueva expresión que crea un ANDgate; el valor de la nueva expresión es un puntero al nuevo objeto.
Mike Seymour
42

Voy a añadir que una de las cosas importantes acerca de shared_ptr's es única cada vez que construir con la siguiente sintaxis:

shared_ptr<Type>(new Type(...));

De esta manera, el puntero "real" a Typees anónimo para su alcance y solo lo mantiene el puntero compartido. Por lo tanto, será imposible que utilice accidentalmente este puntero "real". En otras palabras, nunca hagas esto:

Type* t_ptr = new Type(...);
shared_ptr<Type> t_sptr ptrT(t_ptr);
//t_ptr is still hanging around!  Don't use it!

Aunque esto funcionará, ahora tiene un Type*puntero ( t_ptr) en su función que vive fuera del puntero compartido. Es peligroso usarlo en t_ptrcualquier lugar, porque nunca se sabe cuándo el puntero compartido que lo contiene puede destruirlo, y se producirá un error de segmentación.

Lo mismo ocurre con los punteros que le devuelven otras clases. Si una clase que no escribiste te entrega un puntero, generalmente no es seguro ponerlo en un shared_ptr. No, a menos que esté seguro de que la clase ya no usa ese objeto. Porque si lo coloca en a shared_ptr, y queda fuera del alcance, el objeto se liberará cuando la clase aún lo necesite.

Ken Simon
fuente
8
Todo lo que dijo Ken es bueno y verdadero, pero creo que la forma preferida de llamarlo ahora es auto t_ptr = make_shared<Type>(...);o de manera equivalente shared_ptr<Type> t_ptr = make_shared<Type>(...);, simplemente porque esa forma es más eficiente.
Jason Sydes
@KenSimon, ¿se supone que debe haber una coma ,entre t_sptry ptrTen shared_ptr<Type> t_sptr ptrT(t_ptr);?
Allanqunzi
Aparte de las ambigüedades en el código de ejemplo, una buena advertencia, pero es una pena que tenga que hacerlo, ya que la primera forma es mucho más limpia, y quizás lo más importante, seguramente cualquiera que use un puntero inteligente sabe que existe precisamente para evitar tener archivos crudos peligrosos. punteros flotando alrededor. El párrafo final es interesante; afortunadamente todavía no he trabajado con ninguna biblioteca que me obligue a usar puntos de tipo crudo o poco claro, aunque estoy seguro de que sucederá en algún momento.
underscore_d
20

Aprender a usar punteros inteligentes es, en mi opinión, uno de los pasos más importantes para convertirse en un programador competente en C ++. Como sabe, cada vez que crea un objeto nuevo, en algún momento desea eliminarlo.

Un problema que surge es que, con excepciones, puede ser muy difícil asegurarse de que un objeto siempre se libere una sola vez en todas las rutas de ejecución posibles.

Esta es la razón de RAII: http://en.wikipedia.org/wiki/RAII

Hacer una clase auxiliar con el propósito de asegurarse de que un objeto siempre se elimine una vez en todas las rutas de ejecución.

Ejemplo de una clase como esta es: std :: auto_ptr

Pero a veces te gusta compartir objetos con otros. Solo debe eliminarse cuando nadie lo use más.

Para ayudar con eso, se han desarrollado estrategias de recuento de referencias, pero aún necesita recordar addref y liberar ref manualmente. En esencia, este es el mismo problema que nuevo / eliminar.

Es por eso que boost ha desarrollado boost :: shared_ptr, su puntero inteligente de conteo de referencias para que pueda compartir objetos y no perder memoria involuntariamente.

Con la adición de C ++ tr1, esto ahora también se agrega al estándar c ++, pero se llama std :: tr1 :: shared_ptr <>.

Recomiendo usar el puntero compartido estándar si es posible. ptr_list, ptr_dequeue y también los contenedores especializados de IIRC para tipos de puntero. Los ignoro por ahora.

Entonces, podemos comenzar con su ejemplo:

std::vector<gate*> G; 
G.push_back(new ANDgate);  
G.push_back(new ORgate); 
for(unsigned i=0;i<G.size();++i) 
{ 
  G[i]->Run(); 
} 

El problema aquí es ahora que cada vez que G sale del alcance, filtramos los 2 objetos agregados a G. Vamos a reescribirlo para usar std :: tr1 :: shared_ptr

// Remember to include <memory> for shared_ptr
// First do an alias for std::tr1::shared_ptr<gate> so we don't have to 
// type that in every place. Call it gate_ptr. This is what typedef does.
typedef std::tr1::shared_ptr<gate> gate_ptr;    
// gate_ptr is now our "smart" pointer. So let's make a vector out of it.
std::vector<gate_ptr> G; 
// these smart_ptrs can't be implicitly created from gate* we have to be explicit about it
// gate_ptr (new ANDgate), it's a good thing:
G.push_back(gate_ptr (new ANDgate));  
G.push_back(gate_ptr (new ORgate)); 
for(unsigned i=0;i<G.size();++i) 
{ 
   G[i]->Run(); 
} 

Cuando G sale del alcance, la memoria se recupera automáticamente.

Como ejercicio con el que asedié a los recién llegados a mi equipo es pedirles que escriban su propia clase de puntero inteligente. Luego, una vez que haya terminado, descarte la clase inmediatamente y nunca la vuelva a usar. Es de esperar que haya adquirido un conocimiento crucial sobre cómo funciona un puntero inteligente bajo el capó. Realmente no hay magia.

Solo otro metaprogramador
fuente
Mi instructor me dio un consejo similar sobre cómo escribir mis propias clases, así que lo intentaré con seguridad. TY.
Ahmed
debe usar un iterador para ejecutar todas las puertasfor( auto itt = G.begin(); itt != G.end(); ++itt ){ itt->Run(); }
Guillaume Massé
1
O mejor aún, el nuevo "foreach" en C ++
solo otro metaprogramador
2

La documentación de boost proporciona un ejemplo de inicio bastante bueno: ejemplo shared_ptr (en realidad se trata de un vector de punteros inteligentes) o shared_ptr doc La siguiente respuesta de Johannes Schaub explica bastante bien los punteros inteligentes de boost: explicación de los punteros inteligentes

La idea detrás (en la menor cantidad de palabras posible) ptr_vector es que maneja la desasignación de la memoria detrás de los punteros almacenados por usted: digamos que tiene un vector de punteros como en su ejemplo. Al salir de la aplicación o abandonar el ámbito en el que se define el vector, tendrá que limpiar después de usted mismo (ha asignado dinámicamente ANDgate y ORgate) pero simplemente borrar el vector no lo hará porque el vector está almacenando los punteros y no los objetos reales (no destruirá sino lo que contiene).

 // if you just do
 G.clear() // will clear the vector but you'll be left with 2 memory leaks
 ...
// to properly clean the vector and the objects behind it
for (std::vector<gate*>::iterator it = G.begin(); it != G.end(); it++)
{
  delete (*it);
}

boost :: ptr_vector <> se encargará de lo anterior por usted, lo que significa que desasignará la memoria detrás de los punteros que almacena.

celavek
fuente
shared_ptr es un puntero inteligente, un "envoltorio" brillante para un puntero simple que, digamos, agrega algo de IA a un tipo de puntero. ptr_vector es un contenedor inteligente para punteros, una "envoltura" para un contenedor de punteros.
celavek
entonces ptr_vector es una especie de reemplazo del vector normal?
Ahmed
@Ahmed Supongo que puedes pensarlo así.
celavek
2

A través de Boost puedes hacerlo>

std::vector<boost::any> vecobj;
    boost::shared_ptr<string> sharedString1(new string("abcdxyz!"));    
    boost::shared_ptr<int> sharedint1(new int(10));
    vecobj.push_back(sharedString1);
    vecobj.push_back(sharedint1);

> para insertar un tipo de objeto diferente en su contenedor de vectores. mientras que para acceder tienes que usar any_cast, que funciona como dynamic_cast, espera que funcione para tus necesidades.

user1808932
fuente
1
#include <memory>
#include <iostream>

class SharedMemory {
    public: 
        SharedMemory(int* x):_capture(x){}
        int* get() { return (_capture.get()); }
    protected:
        std::shared_ptr<int> _capture;
};

int main(int , char**){
    SharedMemory *_obj1= new SharedMemory(new int(10));
    SharedMemory *_obj2 = new SharedMemory(*_obj1);
    std::cout << " _obj1: " << *_obj1->get() << " _obj2: " << *_obj2->get()
    << std::endl;
    delete _obj2;

    std::cout << " _obj1: " << *_obj1->get() << std::endl;
    delete _obj1;
    std::cout << " done " << std::endl;
}

Este es un ejemplo de shared_ptr en acción. _obj2 se eliminó pero el puntero sigue siendo válido. la salida es, ./test _obj1: 10 _obj2: 10 _obj2: 10 hecho

Syed Raihan
fuente
0

La mejor manera de agregar diferentes objetos en el mismo contenedor es usar make_shared, vector y bucle basado en rango y tendrás un código agradable, limpio y "legible".

typedef std::shared_ptr<gate> Ptr   
vector<Ptr> myConatiner; 
auto andGate = std::make_shared<ANDgate>();
myConatiner.push_back(andGate );
auto orGate= std::make_shared<ORgate>();
myConatiner.push_back(orGate);

for (auto& element : myConatiner)
    element->run();
Hooman
fuente