我正在开发一个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表示,所以这是我经过多次努力之后决定的方法。
答案 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对象有时可能包含非常大的向量,因此出于性能原因,复制构造函数似乎也是一个坏主意。
#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(){}
};
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
然后,我在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);
...
}