Cython:如何包装一个返回C ++对象的C ++函数?

时间:2015-03-10 20:08:20

标签: python c++ wrapper cython

我正在开发一个Python项目,我希望与已编写的C ++包接口。由于我将在本项目的其他部分使用Cython,我更喜欢使用Cython进行换行。

简而言之,我需要包装一个函数FooBar,它返回一个自定义类类型为Bar的对象。

这是Bar.h:

#include <cstddef> // For size_t
#include <vector>

/* data returned by function FooBar()*/
class Bar {
public:
    size_t X;
    std::vector<size_t> Y;      
    std::vector<double> Z;  
    std::vector<double> M;  
    std::vector<size_t> N;  

};


Bar FooBar(const std::vector<double> & O, size_t P, size_t Q);

和PyBar.pyx:

from libcpp.vector cimport vector

cdef extern from "Bar.h":
    cdef cppclass Bar:
        size_t X
        vector[size_t] Y    
        vector[double] Z    
        vector[double] M 
        vector[size_t] N    
    cdef Bar FooBar(const vector[double] & O, size_t P, size_t Q)

cdef class PyBar:
    cdef Bar *thisptr      # hold a C++ instance which we're wrapping

    def __cinit__(self, O, P, Q):
        C_Bar = FooBar(O, P, Q)
        self.thisptr = &C_Bar

    def __dealloc__(self):
        del self.thisptr

实际问题:这是否是我想要做的正确方法?作为参考,如果我只是试图自己包装类我没有问题:我可以导入模块,使用PyBar()创建对象,并且在类上实现的底层C方法可以工作。问题是尝试包装一个返回C ++类对象的函数。在野外,我实际上永远不会想要创建任何不是由FooBar创建的Bar对象的PyBar表示,所以这是我经过多次努力之后决定的方法。

2 个答案:

答案 0 :(得分:4)

关于问题的第一部分,我认为更优雅的变化是将FooBar定义为:

Bar* FooBar(const std::vector<double> & O, size_t P, size_t Q);

并让它返回一个“new”分配的指针。我想在您原来的Cython代码__cinit__中,您将创建一个堆栈分配Bar,指向该指针,然后它将过期,从而导致最终的灾难。

可能有效的另一种解决方案是保持FooBar返回Bar,更改PyBar以便它启动

cdef class PyBar:
   cdef Bar this_obj

   def __cinit__(self, O, P, Q):
      self.this_obj = FooBar(O,P,Q)

即。保留一个对象而不是一个指针。不需要__dealloc__

我不知道未定义的符号错误......

答案 1 :(得分:1)

在玩了一段时间之后,我发现的唯一半优雅(强调半)解决方案确实涉及修改现有的C ++代码。我在问题中半实现的方法有很多问题,应该被忽略。

也许有更多编写C ++代码经验的人可以提出更好的东西,但是为了后代:

我个人发现修改FooBar()更容易,因此它是Bar的成员函数:它不是返回Bar对象,而是修改它调用的实例。然后,当在Cython中包装Bar时,我没有将FooBar()公开为类方法,但是我在Python的构造函数中调用该方法(因此也就是相应的C ++)对象。这对我有用,因为正如我所说,我真的只打算处理用FooBar()初始化了一些值的Bar对象。

最后,我选择了这种方法而不是使用复制构造函数(这将允许我从FooBar创建的现有Bar对象初始化新的Python /相应的C ++ Bar对象),因为它对我来说似乎更具可读性。复制构造函数方法的优点是只需要修改C中的Bar类定义(添加复制构造函数),如果您对更改FooBar()的实现感到非常不舒服,这可能更好。在我的例子中,由于Bar对象有时可能包含非常大的向量,因此出于性能原因,复制构造函数似乎也是一个坏主意。

这是我的最终代码:

Bar.h:

#include <cstddef> // For size_t
#include <vector>

class Bar {
public:
    size_t X;
    std::vector<size_t> Y;
    std::vector<double> Z;
    std::vector<double> M;
    std::vector<size_t> N;

    void FooBar(const std::vector<double> & O, size_t P, size_t Q);

    ClusterResult(){}


};

PyBar.pyx:

from libcpp.vector cimport vector

cdef extern from "Bar.h":
    cdef cppclass Bar:
        size_t X
        vector[size_t] Y    
        vector[double] Z    
        vector[double] M  
        vector[size_t] N
        Bar()    
        void FooBar(const vector[double] & O, size_t P, size_t Q)


cdef class PyBar:
    cdef Bar *thisptr      # hold a C++ instance which we're wrapping

    def __cinit__(self, O, P, Q):
        self.thisptr = new Bar()
        self.thisptr.FooBar(O, P, Q)

    def __dealloc__(self):
        del self.thisptr


#Below, I implement the public attributes as get/setable properties.
#could have written get/set functions, but this seems more Pythonic.

    property X:
        def __get__(self): return self.thisptr.X
        def __set__(self, X): self.thisptr.X = X

    property Y:
        def __get__(self): return self.thisptr.Y
        def __set__(self, Y): self.thisptr.Y = Y

    property Z:
        def __get__(self): return self.thisptr.Z
        def __set__(self, Z): self.thisptr.centers = Z

    property M:
        def __get__(self): return self.thisptr.M
        def __set__(self, size): self.thisptr.M = M

    property N:
        def __get__(self): return self.thisptr.N
        def __set__(self, size): self.thisptr.N = N

重构FooBar()实现:

然后,我在Bar.cpp中重写了FooBar()的实现,将返回类型更改为void并替换先前由Bar result函数返回的this对象。例如(为了清楚起见,我明确地使用了这个):

Bar FooBar(const std::vector<double> & O, size_t P, size_t Q)
{

    Bar result = new Bar();
    result.X = P + 1;
    result.Z = std::sort(O.begin()+1, O.end());
    const size_t newParam = Q + 2;

    someOtherFunction(newParam, result);

    ...

}

会变成这样:

void Bar::FooBar(const std::vector<double> & O, size_t P, size_t Q)
{

    this->X = P + 1;
    this->Z = std::sort(O.begin()+1, O.end());
    const size_t newParam = Q + 2;

    someOtherFunction(newParam, *this);

...

}