【问题标题】:pybind11 multiple inheritance with custom holder type fails to cast to base typespybind11 具有自定义持有者类型的多重继承无法转换为基本类型
【发布时间】:2018-03-08 16:44:24
【问题描述】:

简介:

这个问题专门针对使用 pybind11 库将 C++ 连接到 python。问题在于包装参与多重继承的 C++ 类,并在 python 中使用用户定义的持有者类型(而不是 unique_ptr 或 shared_ptr)。

问题:

假设有两个纯抽象接口类AB,分别声明foo()bar() 分别是纯抽象方法。 C 类公开继承自 AB,并实现 foo()bar()。 MyHolder 类是一个自定义的、非拥有的智能指针,类似于原始指针,但没有实现指针算术和其他安全运算符。
此外,还有两个函数将 AB 包装在 MyHolder 中。该代码在 C++ 中按预期工作,但从 python 调用时会引发运行时错误。

我试图将代码示例尽可能减少。结果,python 示例实际上泄漏了内存。由于这不会影响可重复性,因此我倾向于使用更少的代码而不是更正确的代码。

C++ 代码

#include <iostream>
#include <memory>

template< typename T >
class MyHolder
{
public:
    explicit MyHolder( T* ptr = nullptr ) : ptr_( ptr ) {}

    template< typename U > MyHolder( MyHolder< U > ptr ) : 
        ptr_( dynamic_cast< T* >( ptr.get() ) ) {}

    T* get() const { return ptr_; }
    T* operator-> () const { return ptr_; }

private:
    T* ptr_;
};

class A
{
public:
    virtual ~A() = default;
    virtual void foo() const = 0;
};

class B
{
public:
    virtual ~B() = default;
    virtual void bar() const = 0;
};

class C : public A, public B
{
public:
    C() = default;
    virtual ~C() = default;
    virtual void foo() const override
    {
        std::cout << "foo" << std::endl;
    }
    virtual void bar() const override
    {
        std::cout << "bar" << std::endl;
    }
};

void testA( MyHolder< A > ptr )
{
    ptr->foo();
}

void testB( MyHolder< B > ptr )
{
    ptr->bar();
}

pybind11 封装代码

#include <pybind11/pybind11.h>
#include "Test.h"

PYBIND11_DECLARE_HOLDER_TYPE( T, MyHolder< T >, true );

PYBIND11_MODULE( test, m )
{
    pybind11::class_< A, MyHolder< A > >( m, "A" )
        .def( "foo", &A::foo );

    pybind11::class_< B, MyHolder< B > >( m, "B" )
        .def( "bar", &B::bar );

    pybind11::class_< C, MyHolder< C >, A, B >( m, "C" )
        .def( pybind11::init<>() );

    m.def( "testA", &testA );
    m.def( "testB", &testB );
}

C++ 测试代码

#include "Test.h"

int main()
{
    auto uptr = std::unique_ptr< C >( new C() );
    auto c = MyHolder< C >( uptr.get() );
    testA( c );
    testB( c );
    return 0;
}

输出:

酒吧

python测试代码

import test
c = test.C()
test.testA( c )
test.testB( c )

输出:
TypeError: testA(): 不兼容的函数参数。支持以下参数类型: 1. (arg0: test.A) -> 无

调用:test.C object at 0x...

检查其他案例

  • 在不涉及多重继承的情况下有效(仅从 AB 继承)
  • 如果使用 std::shared_ptr 而不是 MyHolder,它会起作用
  • 在 python 中,isinstance(c, A)、isinstance(c, B) 和一个自然的 isinstance(c, C) 都返回 True(这很奇怪),但仍然没有向上 -正确投射

【问题讨论】:

    标签: python c++ pybind11


    【解决方案1】:

    我也在 pybind11 问题页面上发布了这个,并收到了一个答案,我将在这里复制给遇到相同问题的其他用户。

    jagerman 的回答:

    这里的问题是,当涉及到多重继承时,我们依赖于与 std::shared_ptr 的别名构造函数(此处为构造函数 (8))兼容的构造函数,它允许我们创建一个包含重铸指针(即 A *)的持有者使原始 C * 保持活动状态(并且,如果它最终成为最后一个对象,则销毁 C * 而不是 A *)。

    如果你添加:

    MyHolder( const MyHolder &other, T* ptr ) : ptr_(ptr) {}
    

    该示例将起作用。 (当然,您的示例持有者并没有进行任何实际的内存管理;实际上它需要支持上面描述的那种别名)。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2020-08-26
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-10-13
      • 1970-01-01
      相关资源
      最近更新 更多