Cython:存储指针时 Python/C++ 之间的内存所有权问题

Cython: Memory ownership issue between Python/C++ when storing pointers

我在使用 Cython 的 Python/C++ 代码之间遇到内存所有权问题。我不确定解决此问题的最佳方法是什么。所以我很感激任何帮助。

我的攻略如下:

  1. 创建一个包含 get/set 个成员的 C++ NetInfo 数据结构。
  2. 在Python中填充这个数据结构(数据是从文件中解析出来的,并完成了SqLiteDB查找)。因此,我需要对 NetInfo 进行 cythonize。
  3. 将 NetInfo 对象传递到 C++ 'processing' 例程中以对其进行操作。这些也将被 cythonized。

对于 NetInfo 对象,我需要存储指向其他 NetInfo 对象的指针以指示两个对象之间的交互。

我的(相关的)C++代码如下:

struct NetInfoData;
class NetInfo {
public:
    NetInfo();
    NetInfo(NetInfo const& rhs);
    virtual ~NetInfo();
    ...
    std::vector<NetInfo*> getBridgedNets() const;
    void addBridgedNet(NetInfo const* ni);  
protected:
      NetInfoData* data_;
};
struct NetInfoData
{
     std::string name;
     ...
     std::vector<NetInfo*>  bridged_nets; <-- NOTE: Storing pointers.
};
NetInfo::NetInfo()
    : data_(0)
{
    std::cout << "Constructor " << this << std::endl;
    data_ = new NetInfoData();
}
NetInfo::~NetInfo()
{
    std::cout << "Destructor " << this << std::endl;
    delete data_;
}
NetInfo::NetInfo(NetInfo const& rhs)
    : data_(0)
{
    std::cout << "Copy constructor " << this << std::endl;
    data_ = new NetInfoData();
    data_->name = rhs.data_->name;
    ...
    data_->bridged_nets = rhs.data_->bridged_nets;
}
std::vector<NetInfo*>
NetInfo::getBridgedNets() const
{
    return data_->bridged_nets;
}

void
NetInfo::addBridgedNet(NetInfo* n)
{
    data_->bridged_nets.push_back(n);
}

我的(相关的)Cython代码如下。它 compiles/works 好的。

from cython.operator cimport dereference as deref
from libcpp.vector cimport vector

cdef extern from 'NetInfo.h':
    cdef cppclass NetInfo:
        NetInfo() except +
        NetInfo(NetInfo&) except +
        ...
        vector[NetInfo*] getBridgedNets()
        void             addBridgedNet(NetInfo*)

cdef class PyNetInfo:
    cdef NetInfo* thisptr

    def __cinit__(self, PyNetInfo ni=None):
        if ni is not None:
            self.thisptr = new NetInfo(deref(ni.thisptr))
        else:
            self.thisptr = new NetInfo()
    def __dealloc__(self):
        del self.thisptr
    ...
    def get_bridged_nets(self):
        cdef PyNetInfo r
        cdef NetInfo* n
        cdef vector[NetInfo*] nets = self.thisptr.getBridgedNets()

        result = []
        for n in nets:
            r = PyNetInfo.__new__(PyNetInfo)
            r.thisptr = n
            result.append(r)
        return result

    def add_bridged_net(self, PyNetInfo ni):
        self.thisptr.addBridgedNet(ni.thisptr)

现在我的Python伪代码如下:

import PyNetInfo as NetInfo

a = NetInfo()               # Create a
Update data members of a    # Populate a

tmp = NetInfo(a)     # Call copy constructor of a
for n in xrange(5):  # a interacts with five other NetInfo objects so create and call them to a via add_bridged_net() 
   x = NetInfo(tmp)  # Call copy constructor to make copy of tmp (not a!!)
   Update data members of x

   a.add_bridged_net(x)   # Store pointer to x in a (a is updated!)

有问题的代码是 x = NetInfo(tmp)。在 第二次迭代 中,分配给 x 的旧内存将被释放,因为 x 现在指向一个新对象。这将导致 a 现在包含无效指针。

样本运行:

create a
Constructor 0x101ecd0

create tmp
Copy constructor 0xd71d30

create bridge x
Copy constructor 0xd71bb0
add bridged net:  

create bridge x
Copy constructor 0xc9f740
Destructor 0xd71bb0   <--- Destructor on old x is called due to reassignment which causes a to contain an invalid pointer (hence, eventually segfault)
add bridged net:

我不太确定如何管理内存来解决这个问题。有人可以帮忙吗?

我在想也许可以使用共享指针?所以在我的 C++ 代码中,我说

typedef std::shared_ptr<NetInfo> NetInfoShPtr;

然后,

std::vector<NetInfo*> bridged_nets -> std::vector<NetInfoShPtr> bridged_nets;

但是我不确定在 cython 方面该怎么做。这行得通还是有其他(更简单?)的方法?感谢您的任何想法。

当你这样做时

a.add_bridged_net(x)

未存储对 x 的引用,仅将指向 NetInfo 实例的指针添加到向量中。由于未引用 python 对象 xx 将被释放,因此相应的指针指向 C++ NetInfo 实例,即向量中将有一个指针指向释放对象。

我能够使用共享指针解决这个问题(让它完成管理的所有脏工作)。唯一的麻烦是现在需要在 Cython 中到处使用大量 deref(self.thisptr) 来调用 C++ get/set 方法 :).

C++ 变化:

class NetInfo
typedef std::shared_ptr<NetInfo> NetInfoShPtr;

class NetInfo {
public:
    NetInfo();
    NetInfo(NetInfo const& rhs);
    virtual ~NetInfo();
    ...
    std::vector<NetInfoShPtr> getBridgedNets() const;
    void addBridgedNet(NetInfoShPtr const& ni);  
protected:
      NetInfoData* data_;
};

Cython 变化:

from cython.operator cimport dereference as deref
from libcpp.vector cimport vector
from libcpp.memory cimport shared_ptr

cdef extern from 'NetInfo.h':
    ctypedef shared_ptr[NetInfo] NetInfoShPtr

    cdef cppclass NetInfo:
        NetInfo() except +
        NetInfo(NetInfo&) except +
        ...
        vector[NetInfoShPtr] getBridgedNets()
        void                 addBridgedNet(NetInfoShPtr&)

cdef class PyNetInfo:
    cdef NetInfoShPtr thisptr

    def __cinit__(self, PyNetInfo ni=None):
        if ni is not None:
            self.thisptr = NetInfoShPtr(new NetInfo(deref(ni.thisptr)))
        else:
            self.thisptr = new NetInfoShPtr(new NetInfo())
    def __dealloc__(self):
        self.thisptr.reset()   # no del, reset the shared pointer
    ...
    def get_bridged_nets(self):
        cdef PyNetInfo r
        cdef NetInfoShPtr n
        cdef vector[NetInfoShPtr] nets = deref(self.thisptr).getBridgedNets()   # Must derefence

        result = []
        for n in nets:
            r = PyNetInfo.__new__(PyNetInfo)
            r.thisptr = n
            result.append(r)
        return result

    def add_bridged_net(self, PyNetInfo ni):
        deref(self.thisptr).addBridgedNet(ni.thisptr)  # Must dereference