2013-04-13 32 views
0

这是我遇到的问题的一个非常简单的例子。 struct Foo包含包含一个int的struct Bar。如果Foo收集垃圾,那么即使仍然存在对该栏的引用,它的内部条也会被删除。防止嵌套的C++结构被父母GC'd删除

Python代码

import example 

def get_bar(): 
    foo = example.Foo() 
    foo.bar.x = 10 
    bar = foo.bar 
    print("before {}".format(bar.x)) 
    return foo.bar # foo (and bar) are deleted when this returns 

bar = get_bar() 
print("after {}".format(bar.x)) 

输出

> before 10 
> after 39656152 

我已经消除了从C++代码的所有指针和引用,希望SWIG将使用相同的值语义,但它仍然在内部将东西转换为Foo *和Bar *。我想我的问题是,我如何说服SWIG在_wrap_Foo_bar_get中制作一个栏的副本?

实施例以下代码:

example.h文件

struct Bar { 
    int x; 
}; 
struct Foo { 
    Bar bar; 
}; 

example.i

%module "example" 
%{ 
    #include "example.h" 
%} 
%include "example.h" 

的CMakeLists.txt

FIND_PACKAGE(SWIG REQUIRED) 
INCLUDE(${SWIG_USE_FILE}) 

FIND_PACKAGE(PythonLibs) 
INCLUDE_DIRECTORIES(${PYTHON_INCLUDE_PATH} .) 

INCLUDE_DIRECTORIES(${CMAKE_CURRENT_SOURCE_DIR}) 

SET(CMAKE_SWIG_FLAGS "") 

SET_SOURCE_FILES_PROPERTIES(example.i PROPERTIES CPLUSPLUS ON) 
SET_SOURCE_FILES_PROPERTIES(example.i PROPERTIES SWIG_FLAGS "-includeall") 
SWIG_ADD_MODULE(example python example.i example.h) 
SWIG_LINK_LIBRARIES(example ${PYTHON_LIBRARIES}) 

这里是产生SWIG方法,其抓住一个参考吧而它的值:

SWIGINTERN PyObject *_wrap_Foo_bar_get(PyObject *SWIGUNUSEDPARM(self), PyObject *args) { 
    PyObject *resultobj = 0; 
    Foo *arg1 = (Foo *) 0 ; 
    void *argp1 = 0 ; 
    int res1 = 0 ; 
    PyObject * obj0 = 0 ; 
    Bar *result = 0 ; 

    if (!PyArg_ParseTuple(args,(char *)"O:Foo_bar_get",&obj0)) SWIG_fail; 
    res1 = SWIG_ConvertPtr(obj0, &argp1,SWIGTYPE_p_Foo, 0 | 0); 
    if (!SWIG_IsOK(res1)) { 
    SWIG_exception_fail(SWIG_ArgError(res1), "in method '" "Foo_bar_get" "', argument " "1"" of type '" "Foo *""'"); 
    } 
    arg1 = reinterpret_cast< Foo * >(argp1); 
    result = (Bar *)& ((arg1)->bar); 
    resultobj = SWIG_NewPointerObj(SWIG_as_voidptr(result), SWIGTYPE_p_Bar, 0 | 0); 
    return resultobj; 
fail: 
    return NULL; 
} 
+3

听起来像是shared_ptr的情况。 –

+0

由于您使用的是SWIG,因此我不会将此作为答案发布,但如果您使用Boost Python进行包装,它会给出诸如'copy_const_reference'和'return_internal_reference'之类的选择来适当地管理生存期(通过复制或通过将容器的生命周期延长到包含的对象的生命周期)。 –

回答

1

SWIG(和Boost Python)的由具有非常不同的数据模型的语言之间的接口被打一场战斗。通过期望那些SWIG包装的对象的行为与其他Python对象完全相同,您正在进行那场更难(无法获得)的战斗。他们不是因为他们不能。 C++和Python数据模型完全不同。

在C++中,嵌入类Foo中的Bar实例是Foo对象的组成部分。该嵌入对象占用的内存是包含Foo对象的整个内存的一部分。当foo超出范围并被销毁时,foo.bar必然超出范围并与其包含的对象一起被销毁。您的foo.bar不可从foo分离。这两个对象具有相同的寿命。

在Python中情况并非如此。包含子对象的Python对象不包含C++意义上的该子对象。包含对象和包含对象的内存是独特且不重叠的。包含的对象改为具有对包含的子对象的引用。这使得Python中的这些子对象可以从包含它们的对象中分离出来。只需获得对该子对象的单独参考引用,并且包含和包含的对象具有不同的生命周期。

解决此问题的一种方法是在您的C++代码中使用智能指针。 SWIG在一定程度上支持这些。解决这个问题的另一个方法是永远不要让它背后丑陋的头。积极处理暴露给SWIG的代码中的数据。如果嵌入该Foo对象中的对象被正确隐藏,该问题就不会出现。使用成员函数,而不是成员数据,你会好得多。

最后一句话:围绕这个问题还有另外一种方法,那就是使用thisown属性。如果你在你的Python函数get_bar中设置了foo.thisown = 0,你就不会有这个问题。但是,你会有泄漏。

+0

这样做很有道理,谢谢你的详细解答。我发现在包装foo.get_bar()而不是foo.bar时我有更多的控制权。这是一个更复杂的问题:如果如果而不是Foo我有一个std :: vector ?这听起来像我没有选择,只能在这种情况下使用shared_ptr? – kylewm

+0

还有很多其他的选择。例如,当你在'std :: vector '中得到一个元素的引用时,你可以设置'bar.thisown = 0'。 Python代码不会“拥有”该向量中的对象。矢量呢。使用SWIG,您必须了解哪些代码,C++代码或Python代码拥有哪些代码。或者你可以在Python中制作一个SWIG包装对象的副本。然后你有两个不同的对象,一个由C++拥有,一个由Python拥有。 –

+0

对不起,我显然还是有点困惑。在'get_bar()'中,'bar.thisown'已经是false了。你的意思是我必须在_vector_上设置'thisown = 0'来防止整个事件被删除? – kylewm