2011-11-22 8 views
6

Me gustaría almacenar una referencia a un objeto como weak_ptr. En C++ puro, las siguientes obras:boost :: python y weak_ptr: cosas que desaparecen

#include <iostream> 

#include <boost/shared_ptr.hpp> 
#include <boost/weak_ptr.hpp> 

using namespace std; 
using namespace boost; 

struct Empty 
{ 
    Empty(){} 
}; 

struct Store 
{ 
    weak_ptr<Empty> value; 
    Store(){}; 

    void setValue(shared_ptr<Empty> v) { 
     cout << "storing " << v << endl; 
     this->value = weak_ptr<Empty>(v); 
     shared_ptr<Empty> v_ok = this->value.lock(); 
     if (v_ok) { 
      cout << "ok, v has been stored" << endl; 
     } 
    } 

    shared_ptr<Empty> getValue() { 
     shared_ptr<Empty> p = this->value.lock(); 
     if (p) { 
      cout << "stored value : " << p << endl; 
     } else { 
      cout << "there's nothing here !" << endl; 
     } 
     return p; 
    } 
}; 

int main() 
{ 
    shared_ptr<Empty> e(new Empty); 
    shared_ptr<Store> st(new Store); 

    st->setValue(e); 
    st->getValue(); 
    return 0; 
} 

compilar y ejecutar esto le dará la siguiente:

%> ./a.out 
storing 0x8c6c008 
ok, v has been stored 
stored value : 0x8c6c008 

Ahora, si yo encapsular que con el impulso de pitón:

#include <iostream> 

#include <boost/shared_ptr.hpp> 
#include <boost/python.hpp> 
#include <boost/weak_ptr.hpp> 

using namespace std; 
using namespace boost; 
using namespace boost::python; 

struct Empty 
{ 
    Empty(){} 
}; 

struct Store 
{ 
    weak_ptr<Empty> value; 
    Store(){}; 

    void setValue(shared_ptr<Empty> v) { 
     cout << "storing " << v << endl; 
     this->value = weak_ptr<Empty>(v); 
     shared_ptr<Empty> v_ok = this->value.lock(); 
     if (v_ok) { 
      cout << "ok, v has been stored" << endl; 
     } 
    } 

    shared_ptr<Empty> getValue() { 
     shared_ptr<Empty> p = this->value.lock(); 
     if (p) { 
      cout << "stored value : " << p << endl; 
     } else { 
      cout << "there's nothing here !" << endl; 
     } 
     return p; 
    } 
}; 

BOOST_PYTHON_MODULE (test) 
{ 
    class_< Empty, shared_ptr<Empty> >("Empty"); 

    class_< Store, shared_ptr<Store> >("Store") 
    .def("get",&Store::getValue) 
    .def("set",&Store::setValue); 
} 

y ahora un pequeño script de python para probarlo

from test import * 

e = Empty() 
st = Store() 

st.set(e) 
st.get() 

... y el resultado es ...

storing 0x9eb2a18 
ok, v has been stored 
there's nothing here ! 

lo que al parecer mientras estoy todavía en el mismo método (setValue), no hay problema al recuperar un shared_ptr de Tienda :: valor. Pero tan pronto como salgo de este contexto, ¡ya no queda nada!

¿Cómo puede ser esto? ¿Python está pasando un nuevo (e inútil) shared_ptr como un argumento para setValue, que luego se destruye al final de la llamada? Estoy perdido aquí.

+1

Hmm ... Agregue un destructor a 'Vacío' que imprima un mensaje y vea si se destruye. Además, ¿qué ocurre si almacena un 'shared_ptr'? –

+1

Supongo que en una ubicación 'shared_ptr' se refiere a' :: boost :: shared_ptr' y en otra se refiere a ':: std :: shared_ptr'. Prefiero usar 'typedef' para extraer nombres de espacios de nombres dispares y tener múltiples declaraciones' using namespace'. – Omnifarious

+1

¿Piensas que está relacionado con esto? http://mail.python.org/pipermail/cplusplus-sig/2009-November/014981.html – HostileFork

Respuesta

4

Esto es extremadamente curioso. He descartado la posibilidad del puntero compartido de aumentar o aumentar, y jugué con algunos controles de cordura, y hasta donde puedo decir es algo que impulsa a python al puntero compartido que lo rompe.

Trazando constructores/destructores de objetos, las vidas de Empty y Store se están gestionando como era de esperar (no se producen copias).

Un sumamente interesante es que shared_from_this sigue trabajando, incluso cuando weak_ptr<>.lock() no lo hace, y, de hecho, un nuevo puntero débil creado a partir de un nuevo puntero compartida (de shared_from_this) funciona.

Así que esto me lleva a the thread linked in the comments, parece que hay algo impulsado por python que está haciendo con el eliminador y el recuento de referencias que está rompiendo el puntero débil.

Inspección de los punteros compartidos en el depurador esto es lo que obtenemos:

Cuando llamamos setValue, esto es lo que parece el argumento como:

1: p = (const 'boost::shared_ptr<Empty>' &) @0x7fff5fbfe720: { 
    px = 0x100346900, 
    pn = { 
    pi_ = 0x100338dd0 
    } 
} 
> p *p.pn.pi_ 
$5 = (boost::detail::sp_counted_impl_pd<void*,boost::python::converter::shared_ptr_deleter>) { 
    <boost::detail::sp_counted_base> = { 
    _vptr$sp_counted_base = 0x10061aa30, 
    use_count_ = 2, 
    weak_count_ = 2 
    }, 
    members of boost::detail::sp_counted_impl_pd<void*,boost::python::converter::shared_ptr_deleter>: 
    ptr = 0x0, 
    del = { 
    owner = { 
     m_p = 0x10049db90 
    } 
    } 
} 

Si creamos un puntero compartida utilizando shared_from_this en el argumento, que se parece a esto:

1: p = (const 'boost::shared_ptr<Empty>' &) @0x7fff5fbfe5e0: { 
    px = 0x100346900, 
    pn = { 
    pi_ = 0x1003468e0 
    } 
} 
> p *p.pn.pi_ 
$4 = (boost::detail::sp_counted_impl_pd<Empty*,boost::detail::sp_ms_deleter<Empty> >) { 
    <boost::detail::sp_counted_base> = { 
    _vptr$sp_counted_base = 0x10061b170, 
    use_count_ = 2, 
    weak_count_ = 2 
    }, 
    members of boost::detail::sp_counted_impl_pd<Empty*,boost::detail::sp_ms_deleter<Empty> >: 
    ptr = 0x0, 
    del = { 
    initialized_ = true, 
    storage_ = { 
     data_ = "\000i4\000\001\000\000\000?h4\000\001\000\000", 
     align_ = {<No data fields>} 
    } 
    } 
} 

Hay una cosa que hay aquí: la dirección del recuento compartido es diferente: esta es una instancia de puntero compartido diferente ... así que de alguna manera hemos creado dos punteros compartidos diferentes en la misma dirección. Esto es algo extremadamente malo, ya que esperamos que en breve genere un doble libre.

Sin embargo, no es así. (Estoy muy interesado en entender esto más allá, si alguien tiene alguna idea?)

No sé por qué no, para ser sincero (presumiblemente hay algo sutil sucediendo aquí), pero en cualquier caso, todo esto apunta a una solución: podemos usar shared_from_this para crear un puntero compartido, del valor pasado, que puede usar para crear un puntero débil que realmente funciona.

Por lo tanto, para concluir, aquí está la solución:

#include <iostream> 
#include <boost/shared_ptr.hpp> 
#include <boost/python.hpp> 
#include <boost/weak_ptr.hpp> 
#include <boost/enable_shared_from_this.hpp> 

namespace bp = boost::python; 

struct Empty: boost::enable_shared_from_this<Empty>{ }; 

struct Store 
{ 
    boost::weak_ptr<Empty> value; 

    void setValue(boost::shared_ptr<Empty> const& v) { 
     value = boost::weak_ptr<Empty>(v->shared_from_this()); 
     boost::shared_ptr<Empty> v_ok = value.lock(); 
     if (v_ok) { 
      std::cout << "ok, v has been stored" << std::endl; 
     } 
    } 

    boost::shared_ptr<Empty> getValue() { 
     boost::shared_ptr<Empty> p = value.lock(); 
     if (p) { 
      std::cout << "stored value : " << p << std::endl; 
     } else { 
      std::cout << "there's nothing here !" << std::endl; 
     } 
     return p; 
    } 
}; 

BOOST_PYTHON_MODULE (libmylibinterface) 
{ 
    bp::class_< Empty, boost::shared_ptr<Empty> >("Empty",bp::init<>()) 
     ; 

    bp::class_< Store, boost::shared_ptr<Store> >("Store") 
     .def("get",&Store::getValue) 
     .def("set",&Store::setValue); 

} 
+0

gracias! En un tema relacionado, este es el segundo error relacionado con el puntero inteligente que tropecé en poco tiempo ([el primero aquí] (http://stackoverflow.com/questions/8203200/boostpython-and-seterase-bearer-behaviour)) . ¿Puedo esperar que la implementación de std :: tr1 sea mejor? – girodt

+0

Curioso, me pregunto si la causa raíz de esto está relacionada. Creo que puedo entender el razonamiento detrás de comparar el recuento de referencias, ya que esa es la identidad de un conjunto de punteros compartidos y, en algunas circunstancias extrañas, uno podría tener dos conjuntos diferentes de punteros compartidos para un objeto compartido, lo que podría ser útil si el eliminador de al menos uno de los dos conjuntos en realidad no estaba eliminando. Todavía algo poco intuitivo ... – James

3

Otra solución consiste en pasar una referencia a la shared_ptr, no el objeto en sí shared_ptr. En ese caso obviamente boost::python-Wrapper no está creando este extraño shared_ptr separado.

Btw. Si miras el tipo de la clase señalada por shared_ptr.pn.pi_ verás que el "original" shared_ptr entregado a Python contiene un puntero a un objeto sp_counted_impl_p<POINTEE_TYPE> mientras que el shared_ptr devuelto desde Python contiene un puntero a a sp_counted_impl_pd<void*, shared_ptr_deleter> object (ver smart_ptr/detail/sp_counted_impl.hpp) La variante pd no contiene una referencia a la punta. Sospecho que este objeto sp_counted_impl_pd hace referencia de alguna manera al objeto sp_counted_impl_p. Esto explicaría por qué no se llama al destructor cuando el recuento de referencia del shared_ptr pasado de nuevo desde Python cae a cero. El hecho de que weak_ptr no funciona en este caso podría ser simplemente un error, entonces ...?