Отображение интерфейса С++ в boost python

Пример кода для иллюстрации:

struct Base
{
  virtual int foo() = 0;
};

struct Derived : public Base
{
  virtual int foo()
  {
    return 42;
  }
};

Base* get_base()
{
  return new Derived;
}

BOOST_PYTHON_MODULE(libTestMod)
{
  py::class_<Base>("Base", py::no_init)
    .def("foo", py::pure_virtual(&Base::foo));

  py::def("get_base", get_base, py::return_internal_reference<>()); //ignore mem leak
}
  • База:: foo не будет переопределена в python
  • База: foo будет реализован в С++, но это не должно быть подвержено python

Пробовал вышеуказанный код, но не смог скомпилировать.

обновление: Ошибка компиляции:

/path/to/boostlib/boost/1.53.0-0/common/include/boost/python/object/value_holder.hpp:66:11: error: cannot declare field 'boost_1_53_0::python::objects::value_holder<Base>::m_held' to be of abstract type 'Base'
Main.C:59:8: note:   because the following virtual functions are pure within 'Base':
Main.C:61:15: note:         virtual int Base::foo()

Ответ 1

Абстрактные классы С++ не могут быть показаны таким образом на Boost.Python. Boost.Python tutorial дает примеры того, как выставлять чистые виртуальные функции. Короче говоря, при декорировании методов с помощью boost::python::pure_virtual необходимо создать тип-оболочку, чтобы позволить С++ полиморфно разрешать виртуальную функцию, а реализация виртуальной функции будет делегировать функцию полиморфно в иерархии объектов Python.

struct BaseWrap : Base, boost::python::wrapper<Base>
{
  int foo()
  {
    return this->get_override("foo")();
  }
};

...

boost::python::class_<BaseWrap>("Base", ...)
  .def("foo", boost::python::pure_virtual(&Base::foo))
  ;

Подробнее о том, когда тип подвергается через boost::python::class_, HeldType по умолчанию подвергается открываемому типу, а HeldType создается внутри объекта Python. В документации class_ указано:

Параметр шаблона:

  • T: класс, обернутый
  • HeldType: указывает тип, который фактически встроен в объект Python, обертывающий экземпляр T [...]. По умолчанию T.

Следовательно, boost::python::class_<Base> завершится с ошибкой, так как T = Base и HeldType = Base, а Boost.Python попытается создать объект HeldType в объект Python, который представляет экземпляр Base. Это экземпляр не будет выполнен, так как Base является абстрактным классом.


Вот полный пример, показывающий использование класса BaseWrap.

#include <boost/python.hpp>

struct Base
{
  virtual int foo() = 0;
  virtual ~Base() {}
};

struct Derived : public Base
{
  virtual int foo()
  {
    return 42;
  }
};

Base* get_base()
{
  return new Derived;
}

namespace python = boost::python;

/// @brief Wrapper that will provide a non-abstract type for Base.
struct BaseWrap : Base, python::wrapper<Base>
{
  BaseWrap() {}

  BaseWrap(const Base& rhs)
    : Base(rhs)
  {}

  int foo()
  {
    return this->get_override("foo")();
  }
};

BOOST_PYTHON_MODULE(example)
{
  python::class_<BaseWrap>("Base")
    .def("foo", python::pure_virtual(&Base::foo));
    ;

  python::def("get_base", &get_base,
              python::return_value_policy<python::manage_new_object>());
}

и его использование:

>>> import example
>>> class Spam(example.Base):
...     pass
... 
>>> Spam().foo()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
RuntimeError: Pure virtual function called
>>> class Egg(example.Base):
...     def foo(self):
...         return 100
... 
>>> e = Egg()
>>> e.foo()
100
>>> d = example.get_base()
>>> d.foo()
42

Можно выделить абстрактный класс в Boost.Python, разоблачив его без инициализатора по умолчанию (boost::python::no_init) и не скопируемого (boost::noncopyable). Отсутствие инициализатора препятствует тому, чтобы типы Python извлекались из него, тем самым предотвращая переопределение. Кроме того, деталь реализации, которая Base::foo() реализована в С++ с помощью Derived, несущественна. Если Python вообще не должен знать о методе foo(), то опустите его через def().

#include <boost/python.hpp>

struct Base
{
  virtual int foo() = 0;
  virtual ~Base() {}
};

struct Derived
  : public Base
{
  virtual int foo()
  {
    return 42;
  }
};

struct OtherDerived
  : public Base
{
  virtual int foo()
  {
    return 24;
  }
};

Base* get_base()
{
  return new Derived;
}

Base* get_other_base()
{
  return new OtherDerived;
}

BOOST_PYTHON_MODULE(example)
{
  namespace python = boost::python;
  python::class_<Base, boost::noncopyable>("Base", python::no_init)
    ;

  python::class_<Derived, python::bases<Base> >("Derived", python::no_init)
    .def("foo", &Base::foo)
    ;

  python::class_<OtherDerived, python::bases<Base> >(
      "OtherDerived", python::no_init)
    ;

  python::def("get_base", &get_base,
              python::return_value_policy<python::manage_new_object>());

  python::def("get_other_base", &get_other_base,
              python::return_value_policy<python::manage_new_object>());
}

Интерактивное использование:

>>> import example
>>> b = example.get_base()
>>> b.foo()
42
>>> b = example.get_other_base()
>>> b.foo()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'OtherDerived' object has no attribute 'foo'

Ответ 2

Абстрактные классы на самом деле CAN должны быть открыты для Boost.Python без оберток. Трюк определяет ваш класс с boost:: noncopyable и избегает оболочек pure_virtual.

Вот скорректированный код (протестирован с Boost.Python 1.47.0 и Python 2.7.6):

#include <boost/python/class.hpp>
#include <boost/python/def.hpp>
#include <boost/python/module.hpp>

struct Base
{
  virtual int foo() = 0;
};

struct Derived : public Base
{
  virtual int foo()
  {
    return 42;
  }
};

Base* get_base()
{
  return new Derived;
}

BOOST_PYTHON_MODULE(libTestMod)
{
    namespace py = boost::python;

    py::class_<Base, boost::noncopyable>("Base", py::no_init)
        .def("foo", &Base::foo);

    py::def("get_base", get_base,
        py::return_value_policy<py::reference_existing_object>()); //ignore mem leak
}

Тестирование:

$ python
Python 2.7.6 (default, Mar 31 2014, 16:04:58) 
[GCC 4.7.3] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import libTestMod
>>> base = libTestMod.get_base()
>>> print base.foo()
42

Ответ 3

Принятый ответ правильный, но Visual Studio 2015 содержит ошибка в компиляторе, которая заставит принятый ответ иметь следующую ошибку ссылки:

Ошибка LNK2001 неразрешенный внешний символ "struct Base const volatile * __cdecl boost:: get_pointer (struct Base const volatile *)" (?? $get_pointer @$$ CDUBase @@@boost @@YAPDUBase @@PDU1 @@Z) test_boost_class

Ниже приведено обходное решение об ошибке компилятора VS2015 для принятого ответа:

namespace boost {
    template <>
    inline Base const volatile * get_pointer(struct Base const volatile *ptr) {
        return ptr;
    }
}