Cython:如何包装 returns C++ 对象的 C++ 函数?

Cython: How to wrap a C++ function that returns a C++ object?

我正在做一个 Python 项目,我想在其中与已经编写的 C++ 包进行交互。由于我将在该项目的其他部分使用 Cython,因此我更愿意使用 Cython 进行包装。

简而言之,我需要包装一个函数 FooBar,它 returns 一个自定义 class 类型 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

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

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

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

我个人发现修改 FooBar() 使其成为 Bar 的成员函数更容易:它现在修改调用它的实例,而不是 return 一个 Bar 对象。然后,当在 Cython 中包装 Bar 时,我不会将 FooBar() 公开为 class 方法,但我会在构造函数中为 Python(以及相应的 C++)对象调用该方法。这对我有用,因为正如我所说,我真的只打算处理已由 FooBar() 使用某些值集初始化的 Bar 对象。

最后,我选择了这种方法而不是使用复制构造函数(这将允许我从 FooBar 创建的现有 Bar 对象初始化一个新的 Python/corresponding C++ Bar 对象),因为它看起来更具可读性大部头书。复制构造函数方法的优点是,只需修改 C 中的 Bar class 定义(添加复制构造函数),如果您真的对更改 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() 的实现,将 return 类型更改为 void 并替换之前由 return 编辑的 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);

...

}

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

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

并让它 return 一个 "new" 分配的指针。我认为在您的原始 Cython 代码中 __cinit__ 您将创建一个堆栈分配的 Bar,获取它的指针,然后它会过期导致最终的灾难。

另一种可行的解决方案是保留 FooBar returning Bar,更改 PyBar 使其启动

cdef class PyBar:
   cdef Bar this_obj

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

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

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