Skip to content

Commit

Permalink
Support multiple inheritance from python
Browse files Browse the repository at this point in the history
This commit allows multiple inheritance of pybind11 classes from
Python, e.g.

    class MyType(Base1, Base2):
        def __init__(self):
            Base1.__init__(self)
            Base2.__init__(self)

where Base1 and Base2 are pybind11-exported classes.

This requires collapsing the various builtin base objects
(pybind11_object_56, ...) introduced in 2.1 into a single
pybind11_object of a fixed size; this fixed size object allocates enough
space to contain either a simple object (one base class & small* holder
instance), or a pointer to a new allocation that can contain an
arbitrary number of base classes and holders, with holder size
unrestricted.

* "small" here means having a sizeof() of at most 2 pointers, which is
enough to fit unique_ptr (sizeof is 1 ptr) and shared_ptr (sizeof is 2
ptrs).

To minimize the performance impact, this repurposes
`internals::registered_types_py` to store a vector of pybind-registered
base types.  For direct-use pybind types (e.g. the `PyA` for a C++ `A`)
this is simply storing the same thing as before, but now in a vector;
for Python-side inherited types, the map lets us avoid having to do a
base class traversal as long as we've seen the class before.  The
change to vector is needed for multiple inheritance: Python types
inheriting from multiple registered bases have one entry per base.
  • Loading branch information
jagerman committed Jun 7, 2017
1 parent f484d95 commit 939a163
Show file tree
Hide file tree
Showing 11 changed files with 835 additions and 346 deletions.
40 changes: 16 additions & 24 deletions docs/advanced/classes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -619,27 +619,19 @@ interspersed with alias types and holder types (discussed earlier in this
document)---pybind11 will automatically find out which is which. The only
requirement is that the first template argument is the type to be declared.

There are two caveats regarding the implementation of this feature:

1. When only one base type is specified for a C++ type that actually has
multiple bases, pybind11 will assume that it does not participate in
multiple inheritance, which can lead to undefined behavior. In such cases,
add the tag ``multiple_inheritance``:

.. code-block:: cpp
py::class_<MyType, BaseType2>(m, "MyType", py::multiple_inheritance());
The tag is redundant and does not need to be specified when multiple base
types are listed.

2. As was previously discussed in the section on :ref:`overriding_virtuals`, it
is easy to create Python types that derive from C++ classes. It is even
possible to make use of multiple inheritance to declare a Python class which
has e.g. a C++ and a Python class as bases. However, any attempt to create a
type that has *two or more* C++ classes in its hierarchy of base types will
fail with a fatal error message: ``TypeError: multiple bases have instance
lay-out conflict``. Core Python types that are implemented in C (e.g.
``dict``, ``list``, ``Exception``, etc.) also fall under this combination
and cannot be combined with C++ types bound using pybind11 via multiple
inheritance.
It is also permitted to inherit multiply from exported C++ classes in Python,
as well as inheriting from multiple Python and/or pybind-exported classes.

There is one caveat regarding the implementation of this feature:

When only one base type is specified for a C++ type that actually has multiple
bases, pybind11 will assume that it does not participate in multiple
inheritance, which can lead to undefined behavior. In such cases, add the tag
``multiple_inheritance`` to the class constructor:

.. code-block:: cpp
py::class_<MyType, BaseType2>(m, "MyType", py::multiple_inheritance());
The tag is redundant and does not need to be specified when multiple base types
are listed.
8 changes: 4 additions & 4 deletions include/pybind11/attr.h
Original file line number Diff line number Diff line change
Expand Up @@ -210,17 +210,17 @@ struct type_record {
/// How large is the underlying C++ type?
size_t type_size = 0;

/// How large is pybind11::instance<type>?
size_t instance_size = 0;
/// How large is the type's holder?
size_t holder_size = 0;

/// The global operator new can be overridden with a class-specific variant
void *(*operator_new)(size_t) = ::operator new;

/// Function pointer to class_<..>::init_holder
void (*init_holder)(PyObject *, const void *) = nullptr;
void (*init_holder)(instance *, const void *) = nullptr;

/// Function pointer to class_<..>::dealloc
void (*dealloc)(PyObject *) = nullptr;
void (*dealloc)(const detail::value_and_holder &) = nullptr;

/// List of base classes of the newly created type
list bases;
Expand Down
Loading

0 comments on commit 939a163

Please sign in to comment.