2013-04-13 33 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