【问题标题】:Inheritance and std::shared_ptr in CythonCython 中的继承和 std::shared_ptr
【发布时间】:2021-08-10 01:19:45
【问题描述】:

假设我在file.h 中有以下简单的 C++ 继承示例:

class Base {};
class Derived : public Base {};

然后,以下代码编译;也就是说,我可以将std::shared_ptr<Derived>分配给std::shared_ptr<Base>

Derived* foo = new Derived();
std::shared_ptr<Derived> shared_foo = std::make_shared<Derived>(*foo);
std::shared_ptr<Base> bar = shared_foo;

假设我已将类型添加到 decl.pxd:

cdef extern from "file.h":
    cdef cppclass Base:
        pass
    cdef cppclass Derived(Base):
        pass

然后,我要做的是在 file.pyx 中模仿 Cython 中的上述 C++ 赋值:

cimport decl
from libcpp.memory cimport make_shared, shared_ptr

def do_stuff():
    cdef decl.Derived* foo = new decl.Derived()
    cdef shared_ptr[decl.Derived] shared_foo = make_shared[decl.Derived](foo)
    cdef shared_ptr[decl.Base] bar = shared_foo

与 C++ 案例不同,现在失败并出现以下错误(使用 Cython 3.0a6):

cdef shared_ptr[decl.Base] bar = shared_foo
                                ^
---------------------------------------------------------------

 Cannot assign type 'shared_ptr[Derived]' to 'shared_ptr[Base]'

我应该期待这种行为吗?有什么方法可以模仿 C++ 示例对 Cython 的作用?

编辑:参见。对于下面接受的答案的 cmets,相关功能已添加到 Cython,并且从 3.0a7 版本开始可用。

【问题讨论】:

    标签: python c++ inheritance cython shared-ptr


    【解决方案1】:

    它应该适用于 Cython>=3.0,因为 @fuglede 做了这个 PR 解决了下面描述的问题(Cython


    问题是,std::shared_ptrwrapper 错过了

    template <class U> shared_ptr& operator= (const shared_ptr<U>& x) noexcept;
    

    属于std::shared_ptr-class。

    如果你像这样修补包装器:

    cdef extern from "<memory>" namespace "std" nogil:
    cdef cppclass shared_ptr[T]:
        ...
        shared_ptr[T]& operator=[Y](const shared_ptr[Y]& ptr)
        #shared_ptr[Y](shared_ptr[Y]&)  isn't accepted
    

    您的代码将编译。

    你可能会问,为什么需要operator= 而不是构造函数shared_ptr[Y],因为:

    ...
    cdef shared_ptr[decl.Base] bar = shared_foo
    

    看起来构造函数 (template &lt;class U&gt; shared_ptr (const shared_ptr&lt;U&gt;&amp; x) noexcept;) 不明确。但这是 Cython 的 C++ 怪癖之一。上面的代码会被翻译成

    std::shared_ptr<Base> __pyx_v_bar;
    ...
    __pyx_v_bar = __pyx_v_shared_foo;
    
    

    而不是

    std::shared_ptr<Base> __pyx_v_bar = __pyx_v_shared_foo;
    

    因此 Cython 将检查 operator= 的存在(对我们来说很幸运,因为 Cython 似乎不支持带模板的构造函数,但对运算符支持)。


    如果您还想在没有修补 memory.pxd 的系统上分发您的模块,您有两个选择:

    1. 自己正确包装std::shared_ptr
    2. 写一个小实用函数,例如
    %%cython
    ...
    cdef extern from *:
        """
        template<typename T1, typename T2>
        void assign_shared_ptr(std::shared_ptr<T1>& lhs, const std::shared_ptr<T2>& rhs){
             lhs = rhs;
        }
        """
        void assign_shared_ptr[T1, T2](shared_ptr[T1]& lhs, shared_ptr[T2]& rhs)
        
    ...
    cdef shared_ptr[Derived] shared_foo
    # cdef shared_ptr[decl.Base] bar = shared_foo
    # must be replaced through:
    cdef shared_ptr[Base] bar 
    assign_shared_ptr(bar, shared_foo)
    ...
    

    这两种选择都有缺点,因此根据您的情况,您可能更喜欢其中一种。

    【讨论】:

    • 相关声明已添加到this pull request,应该会在未来的某个时候提供。
    • @fuglede 感谢您的公关!抱歉,我弄乱了签名 - 不知道它是如何发生的以及为什么它与 cython 一起使用......
    • 当然很好奇,无论哪种方式都有效。无论如何,PR 已经合并,并且功能在 Cython 3.0a7 中可用,所以感谢您的回答和指点!
    【解决方案2】:

    我没有尝试过 Cyton,但是std::shared_ptr 有一个静态转换函数std::static_pointer_cast。我认为这会奏效

    std::shared_ptr<Base> bar = std::static_pointer_cast<Base>(shared_foo);
    

    .

    def do_stuff():
        cdef decl.Derived* foo = new decl.Derived()
        cdef shared_ptr[decl.Derived] shared_foo = make_shared[decl.Derived](foo)
        cdef shared_ptr[decl.Base] bar = static_pointer_cast[decl.Base] shared_foo
    

    附注

    您创建shared_foo 的方式可能不是您想要的。在这里,您首先创建一个动态分配的派生。然后,您将创建一个新的动态分配的共享派生,它是原始副本的副本。

    // this allocates one Derived
    Derived* foo = new Derived(); 
    // This allocates a new copy, it does not take ownership of foo
    std::shared_ptr<Derived> shared_foo = std::make_shared<Derived>(*foo); 
    

    你可能想要的是:

    Derived* foo = new Derived();
    std::shared_ptr<Derived> shared_foo(foo); // This now takes ownership of foo
    

    或者只是:

    // This makes a default constructed shared Derived
    auto shared_foo = std::make_shared<Derived>(); 
    

    【讨论】:

    • 好点。据我(有限)所知,Cython 中没有 shared_foo(foo); 等价物,这意味着生成的代码可能会遇到相同的问题(?)
    • 你说得对,static_pointer_cast[T, U] is a thing,事实上,static_pointer_cast[decl.Base, decl.Derived](shared_foo) 似乎在做我想做的事!
    猜你喜欢
    • 2014-08-27
    • 1970-01-01
    • 2012-05-13
    • 1970-01-01
    • 2023-03-11
    • 2019-03-27
    • 1970-01-01
    • 2023-04-03
    相关资源
    最近更新 更多