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



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

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

这是酒吧:

#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表示,所以这是我在多次挠头后决定的方法。

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

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

并让它返回一个"新"分配的指针。我认为在您的原始 Cython 代码中__cinit__您将创建一个分配的堆栈 Bar,获取该 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__

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

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

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

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

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

这是我的最终代码:

酒吧:

#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);
...
}

最新更新