2013-08-12 51 views
5

在使用Boost.Python和C++時,有時我們會創建使用類本身和boost::shared_ptr<>版本綁定的類。這是非常方便的原因很多,可以在很多地方使用。 但是,當boost::pythonboost::shared_ptr<>返回爲在Python中生成的值並記錄在C++靜態變量上時,該機制似乎無法可靠地工作。在C++靜態boost中保存python生成的值:: shared_ptr

通常情況下,我希望返回的boost::shared_ptr<>擁有一個特殊的刪除程序來處理此問題,但似乎並非如此。看來發生的事情是,返回的boost::shared_ptr只是包裝一個指向Python中產生的值的指針,而沒有任何特殊的刪除操作。這會導致來自雙重刪除(一個來自Python解釋器本身,一個來自C++靜態)的一致崩潰 - 或者至少看起來像它。

要使用下面的代碼重現此行爲,請創建一個test.cc文件,並使用以下腳本進行測試。

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

struct A { 
    std::string _a; 
    A(std::string a): _a(a) {} 
    std::string str() { return _a; } 
}; 

static boost::shared_ptr<A> holder(new A("foo")); 

static void set_holder(boost::shared_ptr<A> a_ptr) { 
    holder = a_ptr; 
} 

static boost::shared_ptr<A> get_holder() { 
    return holder; 
} 

BOOST_PYTHON_MODULE(test) 
{ 
    using namespace boost::python; 

    class_<A, boost::shared_ptr<A> >("A", init<std::string>()) 
    .def("__str__", &A::str) 
    ; 

    def("set_holder", &set_holder); 
    def("get_holder", &get_holder); 
} 

隨着以下Python測試程序:

import test 
print(str(test.get_holder())) 
test.set_holder(test.A('bar')) 
print(str(test.get_holder())) 

編譯(與g++ -I/usr/include/python2.7 -shared -fpic test.cc -lboost_python -lpython2.7 -o test.so)和Linux下運行上述程序(python test.py)(ubuntu的12.10,與Python 2.7和Boost 1.50),導致以下堆棧跟蹤:

#0 0x000000000048aae8 in ??() 
#1 0x00007fa44f85f589 in boost::python::converter::shared_ptr_deleter::operator()(void const*)() from /usr/lib/libboost_python-py27.so.1.50.0 
#2 0x00007fa44fa97cf9 in boost::detail::sp_counted_impl_pd<void*, boost::python::converter::shared_ptr_deleter>::dispose()() 
    from /remote/filer.gx/home.active/aanjos/test.so 
#3 0x00007fa44fa93f9c in boost::detail::sp_counted_base::release()() 
    from /remote/filer.gx/home.active/aanjos/test.so 
#4 0x00007fa44fa9402b in boost::detail::shared_count::~shared_count()() 
    from /remote/filer.gx/home.active/aanjos/test.so 
#5 0x00007fa44fa94404 in boost::shared_ptr<A>::~shared_ptr()() 
    from /remote/filer.gx/home.active/aanjos/test.so 
#6 0x00007fa450337901 in __run_exit_handlers (status=0, 
    listp=0x7fa4506b46a8 <__exit_funcs>, run_list_atexit=true) at exit.c:78 
#7 0x00007fa450337985 in __GI_exit (status=<optimized out>) at exit.c:100 
#8 0x00007fa45031d774 in __libc_start_main (main=0x44b769 <main>, argc=2, 
    ubp_av=0x7fffaa28e2a8, init=<optimized out>, fini=<optimized out>, 
    rtld_fini=<optimized out>, stack_end=0x7fffaa28e298) at libc-start.c:258 
#9 0x00000000004ce6dd in _start() 

這表明在靜態析構函數中發生了雙重刪除。這種行爲似乎在不同平臺之間是一致的。

問題:是否可以實現描述的行爲而不復制boost::python的返回值?在上面的玩具例子中,這很簡單,但在我真正的問題中,A的深層複製是不切實際的。

回答

0

你的問題是python已完成後發生shared_ptr的破壞。請看:

我建議封裝shared_ptr,它沒有額外的清理代碼。 四soulutions,雖然:

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

struct A { 
    std::string _a; 
    A(std::string a): _a(a) {} 
    ~A() { std::cout << "Destruct: " << _a << std::endl; } 
    std::string str() { return _a; } 
}; 

void python_exit(); 

static boost::shared_ptr<A> holder(new A("foo")); 

static boost::shared_ptr<A> get_holder() { 
    return holder; 
} 

static void set_holder(boost::shared_ptr<A> a_ptr) { 
    // The shared_ptr comes with python::converter::shared_ptr_deleter 
    holder = a_ptr; 
} 


// Fix 1: Cleanup while python is running 
// ====================================== 

void reset_holder() { 
    std::cout << "reset" << std::endl; 
    holder.reset(new A("holder without shared_ptr_deleter")); 
} 


// Fix 2: The shared pointer is never deleted (which is a memory leak of a 
//  global varialbe). The contained object is destructed, below. 
// ========================================================================= 

static boost::shared_ptr<A>* holder_ptr = new boost::shared_ptr<A>(
    new A("foo_ptr")); 

static boost::shared_ptr<A> get_holder_ptr() { 
    return *holder_ptr; 
} 

static void set_holder_ptr(boost::shared_ptr<A> a_ptr) { 
    // Note: I know, it's no good to do that here (quick and dirty): 
    Py_AtExit(python_exit); 
    // The shared_ptr comes with python::converter::shared_ptr_deleter 
    *holder_ptr = a_ptr; 
} 

void python_exit() { 
    std::cout << "\n" 
    "Since Python’s internal finalization will have completed before the\n" 
    "cleanup function, no Python APIs should be called in or after exit.\n" 
    "The boost::python::shared_ptr_deleter will do so, though.\n" 
    << std::endl; 
    // Destruction but no deallocation. 
    holder_ptr->get()->~A(); 
} 


// Fix 3: Put a finalizer object into a module. 
// ========================================================================= 

static boost::shared_ptr<A> holder_finalizer(new A("foo_finalizer")); 


struct PythonModuleFinalizer 
{ 
    ~PythonModuleFinalizer(); 
}; 

PythonModuleFinalizer::~PythonModuleFinalizer() { 
    std::cout << "PythonModuleFinalizer" << std::endl; 
    holder_finalizer.reset(
     new A("holder_finalizer without shared_ptr_deleter")); 
} 

static boost::shared_ptr<A> get_holder_finalizer() { 
    return holder_finalizer; 
} 

static void set_holder_finalizer(boost::shared_ptr<A> a_ptr) { 
    // The shared_ptr comes with python::converter::shared_ptr_deleter 
    holder_finalizer = a_ptr; 
} 


// Fix 4: Encapsulate the shared_ptr 
// ========================================================================= 

class B { 
    private: 
    struct I { 
     std::string b; 
     I(const std::string& b): b(b) {} 
     ~I() { std::cout << "Destruct: " << b << std::endl; } 
    }; 

    public: 
    B(std::string b): s(new I(b)) {} 
    std::string str() { return s.get()->b; } 

    private: 
    boost::shared_ptr<I> s; 
}; 

static B holder_encapsulate("foo_encapsulate"); 


static B get_holder_encapsulate() { 
    return holder_encapsulate; 
} 

static void set_holder_encapsulate(B b) { 
    holder_encapsulate = b; 
} 


BOOST_PYTHON_MODULE(test) 
{ 
    using namespace boost::python; 

    class_<A, boost::shared_ptr<A> >("A", init<std::string>()) 
    .def("__str__", &A::str) 
    ; 

    def("set_holder", &set_holder); 
    def("get_holder", &get_holder); 
    def("reset_holder", &reset_holder); 

    def("set_holder_ptr", &set_holder_ptr); 
    def("get_holder_ptr", &get_holder_ptr); 

    object finalizer_class = class_<PythonModuleFinalizer 
     ("PythonModuleFinalizer", init<>()); 
    object finalizer = finalizer_class(); 
    scope().attr("ModuleFinalizer") = finalizer; 
    def("set_holder_finalizer", &set_holder_finalizer); 
    def("get_holder_finalizer", &get_holder_finalizer); 

    class_<B>("B", init<std::string>()) 
    .def("__str__", &B::str) 
    ; 
    def("set_holder_encapsulate", &set_holder_encapsulate); 
    def("get_holder_encapsulate", &get_holder_encapsulate); 

} 

蟒蛇文件:

import test 
print(str(test.get_holder())) 
test.set_holder(test.A('bar')) 
print(str(test.get_holder())) 
test.reset_holder() 

print(str(test.get_holder_ptr())) 
test.set_holder_ptr(test.A('bar_ptr')) 
print(str(test.get_holder_ptr())) 

print(str(test.get_holder_finalizer())) 
test.set_holder_finalizer(test.A('bar_finalizer')) 
print(str(test.get_holder_finalizer())) 

print(str(test.get_holder_encapsulate())) 
test.set_holder_encapsulate(test.B('bar_encapsulate')) 
print(str(test.get_holder_encapsulate())) 

測試的輸出是:

foo 
Destruct: foo 
bar 
reset 
Destruct: bar 
foo_ptr 
Destruct: foo_ptr 
bar_ptr 
foo_finalizer 
Destruct: foo_finalizer 
bar_finalizer 
foo_encapsulate 
Destruct: foo_encapsulate 
bar_encapsulate 
PythonModuleFinalizer 
Destruct: bar_finalizer 

Since Python’s internal finalization will have completed before the 
cleanup function, no Python APIs should be called in or after exit. 
The boost::python::shared_ptr_deleter will do so, though. 

Destruct: bar_ptr 
Destruct: bar_encapsulate 
Destruct: holder without shared_ptr_deleter 
+0

感謝這個答案。這裏有很多信息,所以我需要一些時間來測試我的問題中的每一個。 –

相關問題