假设我在AB.h
中定义了以下C ++代码:
class A {
public:
void foo() {}
};
class B : public A {
public:
void bar() {}
};
我想在Cython中包装指向这些类对象的共享指针,所以我制作了以下pxd
文件:
from libcpp.memory cimport shared_ptr
cdef extern from "AB.h":
cdef cppclass A:
void foo()
cdef cppclass B:
void bar()
cdef class APy:
cdef shared_ptr[A] c_self
cdef class BPy(APy):
cdef shared_ptr[B] c_self # <-- Error compiling Cython file: 'c_self' redeclared
以及以下pyx
文件:
cdef class APy:
def foo(self):
return self.c_self.get().foo()
cdef class BPy(APy):
def bar(self):
return self.c_self.get().bar()
如您所见,它无法编译。我的目标是让BPy从foo
继承APy
python函数,这样我就不必写两次。我可以跳过BPy(APy)
,而只写BPy
,但随后我必须写
def foo(self):
return self.c_self.get().foo()
在BPy
的定义中也是如此。
我可以将c_self
中的BPy
重命名为其他名称(例如c_b_self
),然后在创建以下对象时将我的指针分配给c_self
和c_b_self
BPy
,但是还有实现我目标的更优雅的方式吗?
答案 0 :(得分:2)
这不是您问题的直接答案(如果有一个,将很好奇!)-但一种选择是用pybind11包装-它可以轻松解决此问题。
#include <pybind11/pybind11.h>
#include "AB.h"
namespace py = pybind11;
PYBIND11_MODULE(example, m) {
py::class_<A>(m, "A")
.def(py::init<>())
.def("foo", &A::foo);
py::class_<B, A>(m, "B") // second template param is parent
.def(py::init<>())
.def("bar", &B::bar);
}
from setuptools import setup, Extension
import pybind11
setup(ext_modules=[Extension('example', ['wrapper.cpp'],
include_dirs=[pybind11.get_include()])])
答案 1 :(得分:2)
令人惊讶的是,尽管感觉自然,但没有直接方法使PyB
成为PyA
的子类,毕竟B
是{{1}的子类}!
但是,所需的层次结构在某些微妙的方面违反了Liskov substitution principle。这项原则大致说明了这一点:
如果
A
是B
的子类,则类型A
的对象可以是 替换为A
类型的对象,而不会破坏 程序。
这不是直接显而易见的,因为从Liskov的观点来看,B
和PyA
的公共接口是可以的,但是有一个(隐式)属性使我们的生活更加艰难:
PyB
可以包装PyA
类型的任何对象A
可以包装任何类型为PyB
的对象,也可以做的比<{1>}少!此观察结果意味着将没有解决该问题的漂亮方法,并且您使用不同指针的建议也不错。
我在下面介绍的解决方案中有一个非常相似的想法,只是我使用了强制类型转换(通过支付某种类型安全性可能会稍微提高性能),而不是缓存指针。
要使示例独立,我使用内联C语言普通代码,并使其更通用,我使用没有可为空的构造函数的类:
B
与您的示例不同:
PyB
是%%cython --cplus
cdef extern from *:
"""
#include <iostream>
class A {
protected:
int number;
public:
A(int n):number(n){}
void foo() {std::cout<<"foo "<<number<<std::endl;}
};
class B : public A {
public:
B(int n):A(n){}
void bar() {std::cout<<"bar "<<number<<std::endl;}
};
"""
cdef cppclass A:
A(int n)
void foo()
cdef cppclass B(A): # make clear to Cython, that B inherits from A!
B(int n)
void bar()
...
的子类,即使用B
-因此我们可以省略从A
到cdef cppclass B(A)
的转换以后。这里是类B
的包装:
A
值得注意的细节是:
A
的类型为...
cdef class PyA:
cdef A* thisptr # ptr in order to allow for classes without nullable constructors
cdef void init_ptr(self, A* ptr):
self.thisptr=ptr
def __init__(self, n):
self.init_ptr(new A(n))
def __dealloc__(self):
if NULL != self.thisptr:
del self.thisptr
def foo(self):
self.thisptr.foo()
...
,而不是thisptr
,因为A *
没有可为空的构造函数A
)来保存引用,也许可以考虑使用A
或__dealloc__
,这取决于类的使用方式。std::unique_ptr
的对象时,std::shared_ptr
将自动初始化为A
,因此无需将thisptr
显式设置为nullptr
在thisptr
中(这就是省略nullptr
的原因)。__cinit__
而不使用__cinit__
很快就会变得明显。现在是类__init__
的包装器:
__cinit__
值得注意的细节:
B
用于将...
cdef class PyB(PyA):
def __init__(self, n):
self.init_ptr(new B(n))
cdef B* as_B(self):
return <B*>(self.thisptr) # I know for sure it is of type B*!
def bar(self):
self.as_B().bar()
强制转换为as_B
(实际上是),而不是保留缓存的thisptr
指针。B
和B *
之间有细微的差别:父类的__cinit__
将始终被调用,而父类的__init__
将仅被调用,当类本身没有__cinit__
方法的实现时。因此,我们使用__init__
是因为我们想覆盖/忽略基类的__init__
的设置。现在(打印到std :: out而不是ipython-cell!):
__init__
最后一个想法:我做了一次体验,即使用继承来“保存代码”通常会导致问题,因为一个人由于错误的原因最终会出现“错误的”层次结构。可能还有其他工具可以减少样板代码(例如@chrisb提到的pybind11-framework),该工具更适合此工作。