2012-05-13 43 views
2

我正在公開一個C++對象,意圖在Python中進行子類化。構造這些對象時,會被一些容器C++端引用。 (實際上是std :: map)一個對象只能在明確從這個容器中移除後才能被破壞。然而,它們是由Python管理的,因此在沒有Python引用留下的情況下被銷燬,但仍然被容器引用。增加對構造函數創建的對象的引用計數暴露於boost :: python

我必須告訴Python,當我構建對象時,我保留對它的引用。我找不到任何簡單的方法來做到這一點。我沒有找到任何意味着「將引用計數遞增到返回對象一次」的調用策略。我是否應該實施自己的呼叫政策來做到這一點? (我不知道如何實現調用策略)還是有另一種方法來做到這一點?

+0

誰放置在容器中的對象? – Dani

+0

忽略它的C++構造函數已被Python調用。 –

+0

我意識到我說的是模棱兩可的。我談論的容器只包含引用,而不包含對象本身。它實際上是指針的std :: map。 –

回答

3

我寫了一個特殊的策略,它增加了引用計數到正在構建的對象。

template <class Base = default_call_policies> 
struct incref_return_value_policy : Base 
{ 
    static PyObject *postcall(PyObject *args, PyObject *result) 
    { 
     PyObject *self = PyTuple_GET_ITEM(args, 0); 
     Py_INCREF(self); 
     return result; 
    } 
}; 

然後它可以被用作任何其他政策:

class_<A>("A", init<>()[ incref_return_value_policy<>() ]); 
+0

這適用於我。不過,我對於如何清理內存感到不知所措。我的'A'類析構函數被調用,這讓我感到很驚訝。因爲我增加了python引用,但從來沒有減少過,我認爲我會泄漏該對象。你知道如何解除引用嗎? –

+0

如果在調用Py_Finalize之前沒有減少引用計數並且對象被銷燬,則可能是某處存在錯誤。也許,你的對象在Py_Finalize期間被破壞。 –

+0

它在Py_Finalize之前。我有一個Python循環反覆調用一個函數來創建一個'A'對象。我打印在函數的頂部,也在A的析構函數中打印。 A在循環迭代之間被破壞。什麼樣的錯誤會導致解除引用? –

1

這是一個小例子,它可以做我想要的東西。整個過程圍繞着使用一個「持有者」類,它告訴Boost.Python在Python中構造特定類型的實例時實際使用不同的類,並且它應該將Python對象作爲第一個參數傳遞給自定義的任何構造函數包裝類。您可以在這裏找到更多的信息:

http://www.boost.org/doc/libs/1_48_0/libs/python/doc/v2/class.html

(見特別是 「HeldType語義」 的討論)。

#include "boost/python.hpp" 

namespace bp = boost::python; 

class Base { 
public: 
    virtual double go(int x) const = 0; 
    virtual ~Base() {} 
}; 

class PyBase : public Base { 
public: 
    explicit PyBase(PyObject* self) : _self(self) { 
     Py_INCREF(_self); // THIS LEAKS MEMORY IF THERE'S NO DECREF! 
    } 
    virtual double go(int x) const { 
     return bp::call_method<double>(_self, "go", x); 
    } 
private: 
    PyObject * _self; 
}; 

BOOST_PYTHON_MODULE(example) { 
    bp::class_<Base,PyBase,boost::noncopyable>(
     "Base", 
     bp::init<>() // the PyObject* arg is implicit 
    ) 
     .def("go", &PyBase::go) 
     ; 
} 

但也有一些注意事項:

  • 如果不從Base繼承了Python實現go,你會得到關於無限遞歸無益的Python異常信息。我也不確定如果有一個默認的實現(如果你想通過查看代碼bp::wrapper,它可以做出非常相似的事情)來讓虛擬函數回落到C++。

  • 如果從C++函數通過引用或指針返回Base對象,你會不會有一個包含PyBase對象,除非你居然回來是PyBase對象開始與實例的Python對象(這是有道理的如果你考慮它,當然)。

  • 如果要通過價值回報,你需要添加一個拷貝構造函數,一個PyObject*作爲第一個參數,然後才能從bp::class_呼叫刪除boost::noncopyable模板參數。

  • 構造函數中的Py_INCREF語句告訴Python「您的代碼」正在對此對象進行額外的引用。但是我不清楚你想如何添加相應的Py_DECREF:如果你只有一個std::mapBase*對象,那麼以後就無法獲得PyObject*

  • 解決上述問題的一種方法是將容器bp::handle<>bp::object,並將self中的其中一個放入容器中。但是在那種情況下,你需要確保在程序結束之前清空這些容器,因爲如果在靜態析構函數時調用了一個Python析構函數,就會出現分段錯誤。

+0

當使用boost :: python :: wrapper時,我沒有實現使用你的解決方案,這是處理對python派生類的虛擬調用的典型方法。這就是你用這種方式調用虛擬方法的原因嗎? –

+0

是的。 boost :: python :: wrapper方法更簡單,更自動。它在實現中實際上使用了一種非常類似的方法,但是在這種情況下(因爲它是包裝器的私有數據成員),您不需要訪問所需的'PyObject *'。你可以用boost :: python :: wrapper做的所有事情都可以這樣完成,但是你必須編寫更多的代碼來處理諸如虛擬函數的C++默認實現。 – jbosch

+0

爲了保持包裝類提供的實用程序來自boost :: python,我傾向於在策略中引用計數處理。對於第四點,boost :: python能夠在執行to_python轉換時檢索PyObject *。但它甚至不需要。當python調用將從std :: map中移除對象的函數時,另一個調用策略將遞減refcounter:策略可以直接訪問PyObjects。 –

相關問題