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

时间:2016-05-18 22:21:59

标签: python cython

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

我的策略如下:

  1. 使用get / set成员创建C ++ NetInfo数据结构。
  2. 在Python中填充此数据结构(数据从文件解析并完成SqLiteDB查找)。因此,我需要对NetInfo进行cython化。
  3. 将NetInfo对象传递给C ++“处理”例程以对其进行操作。这些也将被cython化。
  4. 对于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代码如下。它编译/工作正常。

    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方面做些什么。这会有用还是还有其他(更容易?)的方式?谢谢你的任何想法。

2 个答案:

答案 0 :(得分:2)

我能够使用共享指针来解决这个问题(让它完成管理的所有肮脏工作)。现在唯一的麻烦就是需要在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

答案 1 :(得分:1)

当你这样做时

a.add_bridged_net(x)

未存储对x的引用,只有指向NetInfo实例的指针被添加到向量中。由于未引用python对象x,因此将释放x,因此指向C ++ NetInfo实例的对应指针,即向量中将有一个指向已解除分配对象的指针。 / p>