我正在做一个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);
...
}