Inheritance Cython包装类中的复制固有结构 我们假设下面的C++代码定义为i ab.h:

Inheritance Cython包装类中的复制固有结构 我们假设下面的C++代码定义为i ab.h:,inheritance,cython,Inheritance,Cython,我想在Cython中封装指向这些类的对象的共享指针,因此我创建了以下pxd文件: 正如您所看到的,这不会编译。我的目标是让BPy从APy继承foo-python函数,这样我就不必编写两次。我可以跳过BPyAPy,只写BPy,但我必须写 def foo(self): return self.c_self.get().foo() 在BPy的定义中也是如此 我可以将BPy中的c_self重命名为其他名称,例如c_b_self,然后在创建BPy对象时将我的指针同时指向c_self和c_b_se

我想在Cython中封装指向这些类的对象的共享指针,因此我创建了以下pxd文件:

正如您所看到的,这不会编译。我的目标是让BPy从APy继承foo-python函数,这样我就不必编写两次。我可以跳过BPyAPy,只写BPy,但我必须写

def foo(self):
    return self.c_self.get().foo()
在BPy的定义中也是如此


我可以将BPy中的c_self重命名为其他名称,例如c_b_self,然后在创建BPy对象时将我的指针同时指向c_self和c_b_self,但有没有更优雅的方法来实现我的目标?

这不是对你问题的直接回答,如果有,我会很好奇但是一个选择是使用包装,它可以处理这个问题而不需要太多麻烦

包装器
令人惊讶的是,尽管感觉很自然,但并没有直接的方法使PyB成为PyA的子类,-毕竟B是a的子类

但是,所需的层次结构在某些微妙的方面违反了。这一原则大致说明了以下几点:

如果B是a的子类,那么a类型的对象可以是 替换为类型B的对象,但不破坏 节目

这并不明显,因为从Liskov的角度来看,PyA和PyB的公共接口还可以,但有一个隐含的特性使我们的生活更加艰难:

PyA可以包装任何类型的对象 PyB可以包装任何类型B的对象,也可以做得比PyB少! 这一观察结果意味着这个问题不会有完美的解决方案,而你使用不同指针的建议也没那么糟糕

bellow给出的解决方案有一个非常相似的想法,只是我使用了一个cast,而不是缓存指针,它可能通过支付一些类型安全性来稍微提高性能

为了使示例独立,我使用inline-C-verbatim代码,为了使其更通用,我使用了不带可空构造函数的类:

%%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()
 ...
值得注意的细节包括:

thisptr的类型是A*而不是A,因为A没有可为null的构造函数 我使用原始指针,因此需要保存引用,可能可以考虑使用std::unique\u ptr或std::shared\u ptr,具体取决于类的使用方式。 创建类A的对象时,thisptr会自动初始化为nullptr,因此不需要在uu cinit_uu中显式地将thisptr设置为nullptr,这就是省略u cinit_uu的原因。 为什么使用“初始”和“不使用”将在一段时间内变得显而易见。 现在是B类的包装器:


最后一个想法:我有过这样的经历,使用继承来保存代码常常会导致问题,因为最终会因为错误的原因导致错误的层次结构。可能还有其他工具可以减少模板代码,比如@chrisb提到的pybind11框架,它们更适合这项工作。

您可能会从这个问题中获得一些见解。正如@克里斯德指出的那样,为了包装复杂的C++结构,PybDun11可能是一种更简单的方法。但它隐藏了太多的细节。为了更好地使用它,对C++语义和一些棘手的模板技术有很好的理解。
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()
def foo(self):
    return self.c_self.get().foo()
#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()])])
%%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()
 ...
...
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()
...
...
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()  
>>> PyB(42).foo()
foo 42  
>>> PyB(42).bar()
bar 42