Commit e45c2114 by Jason Rhinelander

Support multiple inheritance from python

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.
parent caedf74a
...@@ -619,27 +619,19 @@ interspersed with alias types and holder types (discussed earlier in this ...@@ -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 document)---pybind11 will automatically find out which is which. The only
requirement is that the first template argument is the type to be declared. requirement is that the first template argument is the type to be declared.
There are two caveats regarding the implementation of this feature: 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.
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 There is one caveat regarding the implementation of this feature:
multiple inheritance, which can lead to undefined behavior. In such cases,
add the tag ``multiple_inheritance``: 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
.. code-block:: cpp inheritance, which can lead to undefined behavior. In such cases, add the tag
``multiple_inheritance`` to the class constructor:
py::class_<MyType, BaseType2>(m, "MyType", py::multiple_inheritance());
.. code-block:: cpp
The tag is redundant and does not need to be specified when multiple base
types are listed. py::class_<MyType, BaseType2>(m, "MyType", py::multiple_inheritance());
2. As was previously discussed in the section on :ref:`overriding_virtuals`, it The tag is redundant and does not need to be specified when multiple base types
is easy to create Python types that derive from C++ classes. It is even are listed.
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.
...@@ -210,17 +210,17 @@ struct type_record { ...@@ -210,17 +210,17 @@ struct type_record {
/// How large is the underlying C++ type? /// How large is the underlying C++ type?
size_t type_size = 0; size_t type_size = 0;
/// How large is pybind11::instance<type>? /// How large is the type's holder?
size_t instance_size = 0; size_t holder_size = 0;
/// The global operator new can be overridden with a class-specific variant /// The global operator new can be overridden with a class-specific variant
void *(*operator_new)(size_t) = ::operator new; void *(*operator_new)(size_t) = ::operator new;
/// Function pointer to class_<..>::init_holder /// 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 /// 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 of base classes of the newly created type
list bases; list bases;
......
...@@ -346,21 +346,77 @@ NAMESPACE_BEGIN(detail) ...@@ -346,21 +346,77 @@ NAMESPACE_BEGIN(detail)
inline static constexpr int log2(size_t n, int k = 0) { return (n <= 1) ? k : log2(n >> 1, k + 1); } inline static constexpr int log2(size_t n, int k = 0) { return (n <= 1) ? k : log2(n >> 1, k + 1); }
// Returns the size as a multiple of sizeof(void *), rounded up.
inline static constexpr size_t size_in_ptrs(size_t s) { return 1 + ((s - 1) >> log2(sizeof(void *))); }
inline std::string error_string(); inline std::string error_string();
/// Core part of the 'instance' type which POD (needed to be able to use 'offsetof') /**
template <typename type> struct instance_essentials { * The space to allocate for simple layout instance holders (see below) in multiple of the size of
* a pointer (e.g. 2 means 16 bytes on 64-bit architectures). The default is the minimum required
* to holder either a std::unique_ptr or std::shared_ptr (which is almost always
* sizeof(std::shared_ptr<T>)).
*/
constexpr size_t instance_simple_holder_in_ptrs() {
static_assert(sizeof(std::shared_ptr<int>) >= sizeof(std::unique_ptr<int>),
"pybind assumes std::shared_ptrs are at least as big as std::unique_ptrs");
return size_in_ptrs(sizeof(std::shared_ptr<int>));
}
// Forward declarations
struct type_info;
struct value_and_holder;
/// The 'instance' type which needs to be standard layout (need to be able to use 'offsetof')
struct instance {
PyObject_HEAD PyObject_HEAD
type *value; /// Storage for pointers and holder; see simple_layout, below, for a description
union {
void *simple_value_holder[1 + instance_simple_holder_in_ptrs()];
struct {
void **values_and_holders;
bool *holder_constructed;
} nonsimple;
};
/// Weak references (needed for keep alive):
PyObject *weakrefs; PyObject *weakrefs;
/// If true, the pointer is owned which means we're free to manage it with a holder.
bool owned : 1; bool owned : 1;
bool holder_constructed : 1; /**
* An instance has two possible value/holder layouts.
*
* Simple layout (when this flag is true), means the `simple_value_holder` is set with a pointer
* and the holder object governing that pointer, i.e. [val1*][holder]. This layout is applied
* whenever there is no python-side multiple inheritance of bound C++ types *and* the type's
* holder will fit in the default space (which is large enough to hold either a std::unique_ptr
* or std::shared_ptr).
*
* Non-simple layout applies when using custom holders that require more space than `shared_ptr`
* (which is typically the size of two pointers), or when multiple inheritance is used on the
* python side. Non-simple layout allocates the required amount of memory to have multiple
* bound C++ classes as parents. Under this layout, `nonsimple.values_and_holders` is set to a
* pointer to allocated space of the required space to hold a a sequence of value pointers and
* holders followed by a set of holder-constructed flags (1 byte each), i.e.
* [val1*][holder1][val2*][holder2]...[bb...] where each [block] is rounded up to a multiple of
* `sizeof(void *)`. `nonsimple.holder_constructed` is, for convenience, a pointer to the
* beginning of the [bb...] block (but not independently allocated).
*/
bool simple_layout : 1;
/// For simple layout, tracks whether the holder has been constructed
bool simple_holder_constructed : 1;
/// Initializes all of the above type/values/holders data
void allocate_layout();
/// Destroys/deallocates all of the above
void deallocate_layout();
/// Returns the value_and_holder wrapper for the given type (or the first, if `find_type`
/// omitted)
value_and_holder get_value_and_holder(const type_info *find_type = nullptr);
}; };
/// PyObject wrapper around generic types, includes a special holder type that is responsible for lifetime management static_assert(std::is_standard_layout<instance>::value, "Internal error: `pybind11::detail::instance` is not standard layout!");
template <typename type, typename holder_type = std::unique_ptr<type>> struct instance : instance_essentials<type> {
holder_type holder;
};
struct overload_hash { struct overload_hash {
inline size_t operator()(const std::pair<const PyObject *, const char *>& v) const { inline size_t operator()(const std::pair<const PyObject *, const char *>& v) const {
...@@ -372,23 +428,20 @@ struct overload_hash { ...@@ -372,23 +428,20 @@ struct overload_hash {
/// Internal data structure used to track registered instances and types /// Internal data structure used to track registered instances and types
struct internals { struct internals {
std::unordered_map<std::type_index, void*> registered_types_cpp; // std::type_index -> type_info std::unordered_map<std::type_index, void*> registered_types_cpp; // std::type_index -> type_info
std::unordered_map<const void *, void*> registered_types_py; // PyTypeObject* -> type_info std::unordered_map<PyTypeObject *, std::vector<type_info *>> registered_types_py; // PyTypeObject* -> base type_info(s)
std::unordered_multimap<const void *, void*> registered_instances; // void * -> PyObject* std::unordered_multimap<const void *, instance*> registered_instances; // void * -> instance*
std::unordered_set<std::pair<const PyObject *, const char *>, overload_hash> inactive_overload_cache; std::unordered_set<std::pair<const PyObject *, const char *>, overload_hash> inactive_overload_cache;
std::unordered_map<std::type_index, std::vector<bool (*)(PyObject *, void *&)>> direct_conversions; std::unordered_map<std::type_index, std::vector<bool (*)(PyObject *, void *&)>> direct_conversions;
std::forward_list<void (*) (std::exception_ptr)> registered_exception_translators; std::forward_list<void (*) (std::exception_ptr)> registered_exception_translators;
std::unordered_map<std::string, void *> shared_data; // Custom data to be shared across extensions std::unordered_map<std::string, void *> shared_data; // Custom data to be shared across extensions
PyTypeObject *static_property_type; PyTypeObject *static_property_type;
PyTypeObject *default_metaclass; PyTypeObject *default_metaclass;
std::unordered_map<size_t, PyObject *> bases; // one base type per `instance_size` (very few) PyObject *instance_base;
#if defined(WITH_THREAD) #if defined(WITH_THREAD)
decltype(PyThread_create_key()) tstate = 0; // Usually an int but a long on Cygwin64 with Python 3.x decltype(PyThread_create_key()) tstate = 0; // Usually an int but a long on Cygwin64 with Python 3.x
PyInterpreterState *istate = nullptr; PyInterpreterState *istate = nullptr;
#endif #endif
/// Return the appropriate base type for the given instance size
PyObject *get_base(size_t instance_size);
}; };
/// Return a reference to the current 'internals' information /// Return a reference to the current 'internals' information
......
...@@ -293,7 +293,7 @@ protected: ...@@ -293,7 +293,7 @@ protected:
if (!chain) { if (!chain) {
/* No existing overload was found, create a new function object */ /* No existing overload was found, create a new function object */
rec->def = new PyMethodDef(); rec->def = new PyMethodDef();
memset(rec->def, 0, sizeof(PyMethodDef)); std::memset(rec->def, 0, sizeof(PyMethodDef));
rec->def->ml_name = rec->name; rec->def->ml_name = rec->name;
rec->def->ml_meth = reinterpret_cast<PyCFunction>(*dispatcher); rec->def->ml_meth = reinterpret_cast<PyCFunction>(*dispatcher);
rec->def->ml_flags = METH_VARARGS | METH_KEYWORDS; rec->def->ml_flags = METH_VARARGS | METH_KEYWORDS;
...@@ -707,10 +707,8 @@ protected: ...@@ -707,10 +707,8 @@ protected:
return nullptr; return nullptr;
} else { } else {
if (overloads->is_constructor) { if (overloads->is_constructor) {
/* When a constructor ran successfully, the corresponding auto tinfo = get_type_info((PyTypeObject *) overloads->scope.ptr());
holder type (e.g. std::unique_ptr) must still be initialized. */ tinfo->init_holder(reinterpret_cast<instance *>(parent.ptr()), nullptr);
auto tinfo = get_type_info(Py_TYPE(parent.ptr()));
tinfo->init_holder(parent.ptr(), nullptr);
} }
return result.ptr(); return result.ptr();
} }
...@@ -727,7 +725,7 @@ public: ...@@ -727,7 +725,7 @@ public:
if (!options::show_user_defined_docstrings()) doc = nullptr; if (!options::show_user_defined_docstrings()) doc = nullptr;
#if PY_MAJOR_VERSION >= 3 #if PY_MAJOR_VERSION >= 3
PyModuleDef *def = new PyModuleDef(); PyModuleDef *def = new PyModuleDef();
memset(def, 0, sizeof(PyModuleDef)); std::memset(def, 0, sizeof(PyModuleDef));
def->m_name = name; def->m_name = name;
def->m_doc = doc; def->m_doc = doc;
def->m_size = -1; def->m_size = -1;
...@@ -830,6 +828,7 @@ protected: ...@@ -830,6 +828,7 @@ protected:
tinfo->cpptype = rec.type; tinfo->cpptype = rec.type;
tinfo->type_size = rec.type_size; tinfo->type_size = rec.type_size;
tinfo->operator_new = rec.operator_new; tinfo->operator_new = rec.operator_new;
tinfo->holder_size_in_ptrs = size_in_ptrs(rec.holder_size);
tinfo->init_holder = rec.init_holder; tinfo->init_holder = rec.init_holder;
tinfo->dealloc = rec.dealloc; tinfo->dealloc = rec.dealloc;
tinfo->simple_type = true; tinfo->simple_type = true;
...@@ -840,7 +839,7 @@ protected: ...@@ -840,7 +839,7 @@ protected:
tinfo->direct_conversions = &internals.direct_conversions[tindex]; tinfo->direct_conversions = &internals.direct_conversions[tindex];
tinfo->default_holder = rec.default_holder; tinfo->default_holder = rec.default_holder;
internals.registered_types_cpp[tindex] = tinfo; internals.registered_types_cpp[tindex] = tinfo;
internals.registered_types_py[m_ptr] = tinfo; internals.registered_types_py[(PyTypeObject *) m_ptr] = { tinfo };
if (rec.bases.size() > 1 || rec.multiple_inheritance) { if (rec.bases.size() > 1 || rec.multiple_inheritance) {
mark_parents_nonsimple(tinfo->type); mark_parents_nonsimple(tinfo->type);
...@@ -923,7 +922,6 @@ public: ...@@ -923,7 +922,6 @@ public:
using type_alias = detail::exactly_one_t<is_subtype, void, options...>; using type_alias = detail::exactly_one_t<is_subtype, void, options...>;
constexpr static bool has_alias = !std::is_void<type_alias>::value; constexpr static bool has_alias = !std::is_void<type_alias>::value;
using holder_type = detail::exactly_one_t<is_holder, std::unique_ptr<type>, options...>; using holder_type = detail::exactly_one_t<is_holder, std::unique_ptr<type>, options...>;
using instance_type = detail::instance<type, holder_type>;
static_assert(detail::all_of<is_valid_class_option<options>...>::value, static_assert(detail::all_of<is_valid_class_option<options>...>::value,
"Unknown/invalid class_ template parameters provided"); "Unknown/invalid class_ template parameters provided");
...@@ -947,7 +945,7 @@ public: ...@@ -947,7 +945,7 @@ public:
record.name = name; record.name = name;
record.type = &typeid(type); record.type = &typeid(type);
record.type_size = sizeof(conditional_t<has_alias, type_alias, type>); record.type_size = sizeof(conditional_t<has_alias, type_alias, type>);
record.instance_size = sizeof(instance_type); record.holder_size = sizeof(holder_type);
record.init_holder = init_holder; record.init_holder = init_holder;
record.dealloc = dealloc; record.dealloc = dealloc;
record.default_holder = std::is_same<holder_type, std::unique_ptr<type>>::value; record.default_holder = std::is_same<holder_type, std::unique_ptr<type>>::value;
...@@ -1139,53 +1137,57 @@ public: ...@@ -1139,53 +1137,57 @@ public:
private: private:
/// Initialize holder object, variant 1: object derives from enable_shared_from_this /// Initialize holder object, variant 1: object derives from enable_shared_from_this
template <typename T> template <typename T>
static void init_holder_helper(instance_type *inst, const holder_type * /* unused */, const std::enable_shared_from_this<T> * /* dummy */) { static void init_holder_helper(detail::instance *inst, detail::value_and_holder &v_h,
const holder_type * /* unused */, const std::enable_shared_from_this<T> * /* dummy */) {
try { try {
auto sh = std::dynamic_pointer_cast<typename holder_type::element_type>(inst->value->shared_from_this()); auto sh = std::dynamic_pointer_cast<typename holder_type::element_type>(
v_h.value_ptr<type>()->shared_from_this());
if (sh) { if (sh) {
new (&inst->holder) holder_type(std::move(sh)); new (&v_h.holder<holder_type>()) holder_type(std::move(sh));
inst->holder_constructed = true; v_h.set_holder_constructed();
} }
} catch (const std::bad_weak_ptr &) {} } catch (const std::bad_weak_ptr &) {}
if (!inst->holder_constructed && inst->owned) {
new (&inst->holder) holder_type(inst->value); if (!v_h.holder_constructed() && inst->owned) {
inst->holder_constructed = true; new (&v_h.holder<holder_type>()) holder_type(v_h.value_ptr<type>());
v_h.set_holder_constructed();
} }
} }
static void init_holder_from_existing(instance_type *inst, const holder_type *holder_ptr, static void init_holder_from_existing(const detail::value_and_holder &v_h,
std::true_type /*is_copy_constructible*/) { const holder_type *holder_ptr, std::true_type /*is_copy_constructible*/) {
new (&inst->holder) holder_type(*holder_ptr); new (&v_h.holder<holder_type>()) holder_type(*reinterpret_cast<const holder_type *>(holder_ptr));
} }
static void init_holder_from_existing(instance_type *inst, const holder_type *holder_ptr, static void init_holder_from_existing(const detail::value_and_holder &v_h,
std::false_type /*is_copy_constructible*/) { const holder_type *holder_ptr, std::false_type /*is_copy_constructible*/) {
new (&inst->holder) holder_type(std::move(*const_cast<holder_type *>(holder_ptr))); new (&v_h.holder<holder_type>()) holder_type(std::move(*const_cast<holder_type *>(holder_ptr)));
} }
/// Initialize holder object, variant 2: try to construct from existing holder object, if possible /// Initialize holder object, variant 2: try to construct from existing holder object, if possible
static void init_holder_helper(instance_type *inst, const holder_type *holder_ptr, const void * /* dummy */) { static void init_holder_helper(detail::instance *inst, detail::value_and_holder &v_h,
const holder_type *holder_ptr, const void * /* dummy -- not enable_shared_from_this<T>) */) {
if (holder_ptr) { if (holder_ptr) {
init_holder_from_existing(inst, holder_ptr, std::is_copy_constructible<holder_type>()); init_holder_from_existing(v_h, holder_ptr, std::is_copy_constructible<holder_type>());
inst->holder_constructed = true; v_h.set_holder_constructed();
} else if (inst->owned || detail::always_construct_holder<holder_type>::value) { } else if (inst->owned || detail::always_construct_holder<holder_type>::value) {
new (&inst->holder) holder_type(inst->value); new (&v_h.holder<holder_type>()) holder_type(v_h.value_ptr<type>());
inst->holder_constructed = true; v_h.set_holder_constructed();
} }
} }
/// Initialize holder object of an instance, possibly given a pointer to an existing holder /// Initialize holder object of an instance, possibly given a pointer to an existing holder
static void init_holder(PyObject *inst_, const void *holder_ptr) { static void init_holder(detail::instance *inst, const void *holder_ptr) {
auto inst = (instance_type *) inst_; auto v_h = inst->get_value_and_holder(detail::get_type_info(typeid(type)));
init_holder_helper(inst, (const holder_type *) holder_ptr, inst->value); init_holder_helper(inst, v_h, (const holder_type *) holder_ptr, v_h.value_ptr<type>());
} }
static void dealloc(PyObject *inst_) { /// Deallocates an instance; via holder, if constructed; otherwise via operator delete.
instance_type *inst = (instance_type *) inst_; static void dealloc(const detail::value_and_holder &v_h) {
if (inst->holder_constructed) if (v_h.holder_constructed())
inst->holder.~holder_type(); v_h.holder<holder_type>().~holder_type();
else if (inst->owned) else
detail::call_operator_delete(inst->value); detail::call_operator_delete(v_h.value_ptr<type>());
} }
static detail::function_record *get_function_record(handle h) { static detail::function_record *get_function_record(handle h) {
...@@ -1349,6 +1351,25 @@ PYBIND11_NOINLINE inline void keep_alive_impl(size_t Nurse, size_t Patient, func ...@@ -1349,6 +1351,25 @@ PYBIND11_NOINLINE inline void keep_alive_impl(size_t Nurse, size_t Patient, func
); );
} }
inline std::pair<decltype(internals::registered_types_py)::iterator, bool> all_type_info_get_cache(PyTypeObject *type) {
auto res = get_internals().registered_types_py
#ifdef z__cpp_lib_unordered_map_try_emplace
.try_emplace(type);
#else
.emplace(type, std::vector<detail::type_info *>());
#endif
if (res.second) {
// New cache entry created; set up a weak reference to automatically remove it if the type
// gets destroyed:
weakref((PyObject *) type, cpp_function([type](handle wr) {
get_internals().registered_types_py.erase(type);
wr.dec_ref();
})).release();
}
return res;
}
template <typename Iterator, typename Sentinel, bool KeyIterator, return_value_policy Policy> template <typename Iterator, typename Sentinel, bool KeyIterator, return_value_policy Policy>
struct iterator_state { struct iterator_state {
Iterator it; Iterator it;
......
...@@ -169,7 +169,7 @@ public: ...@@ -169,7 +169,7 @@ public:
auto &internals = py::detail::get_internals(); auto &internals = py::detail::get_internals();
const std::type_index *t1 = nullptr, *t2 = nullptr; const std::type_index *t1 = nullptr, *t2 = nullptr;
try { try {
auto *type_info = internals.registered_types_py.at(class_.ptr()); auto *type_info = internals.registered_types_py.at((PyTypeObject *) class_.ptr()).at(0);
for (auto &p : internals.registered_types_cpp) { for (auto &p : internals.registered_types_cpp) {
if (p.second == type_info) { if (p.second == type_info) {
if (t1) { if (t1) {
......
...@@ -23,6 +23,11 @@ struct Base2 { ...@@ -23,6 +23,11 @@ struct Base2 {
int i; int i;
}; };
template <int N> struct BaseN {
BaseN(int i) : i(i) { }
int i;
};
struct Base12 : Base1, Base2 { struct Base12 : Base1, Base2 {
Base12(int i, int j) : Base1(i), Base2(j) { } Base12(int i, int j) : Base1(i), Base2(j) { }
}; };
...@@ -45,6 +50,15 @@ test_initializer multiple_inheritance([](py::module &m) { ...@@ -45,6 +50,15 @@ test_initializer multiple_inheritance([](py::module &m) {
py::class_<MIType, Base12>(m, "MIType") py::class_<MIType, Base12>(m, "MIType")
.def(py::init<int, int>()); .def(py::init<int, int>());
// Many bases for testing that multiple inheritance from many classes (i.e. requiring extra
// space for holder constructed flags) works.
#define PYBIND11_BASEN(N) py::class_<BaseN<N>>(m, "BaseN" #N).def(py::init<int>()).def("f" #N, [](BaseN<N> &b) { return b.i + N; })
PYBIND11_BASEN( 1); PYBIND11_BASEN( 2); PYBIND11_BASEN( 3); PYBIND11_BASEN( 4);
PYBIND11_BASEN( 5); PYBIND11_BASEN( 6); PYBIND11_BASEN( 7); PYBIND11_BASEN( 8);
PYBIND11_BASEN( 9); PYBIND11_BASEN(10); PYBIND11_BASEN(11); PYBIND11_BASEN(12);
PYBIND11_BASEN(13); PYBIND11_BASEN(14); PYBIND11_BASEN(15); PYBIND11_BASEN(16);
PYBIND11_BASEN(17);
// Uncommenting this should result in a compile time failure (MI can only be specified via // Uncommenting this should result in a compile time failure (MI can only be specified via
// template parameters because pybind has to know the types involved; see discussion in #742 for // template parameters because pybind has to know the types involved; see discussion in #742 for
// details). // details).
......
...@@ -53,15 +53,174 @@ def test_multiple_inheritance_mix2(): ...@@ -53,15 +53,174 @@ def test_multiple_inheritance_mix2():
assert mt.bar() == 4 assert mt.bar() == 4
def test_multiple_inheritance_error(): def test_multiple_inheritance_python():
"""Inheriting from multiple C++ bases in Python is not supported"""
from pybind11_tests import Base1, Base2 from pybind11_tests import Base1, Base2
with pytest.raises(TypeError) as excinfo: class MI1(Base1, Base2):
# noinspection PyUnusedLocal def __init__(self, i, j):
class MI(Base1, Base2): Base1.__init__(self, i)
pass Base2.__init__(self, j)
assert "Can't inherit from multiple C++ classes in Python" in str(excinfo.value)
class B1(object):
def v(self):
return 1
class MI2(B1, Base1, Base2):
def __init__(self, i, j):
B1.__init__(self)
Base1.__init__(self, i)
Base2.__init__(self, j)
class MI3(MI2):
def __init__(self, i, j):
MI2.__init__(self, i, j)
class MI4(MI3, Base2):
def __init__(self, i, j, k):
MI2.__init__(self, j, k)
Base2.__init__(self, i)
class MI5(Base2, B1, Base1):
def __init__(self, i, j):
B1.__init__(self)
Base1.__init__(self, i)
Base2.__init__(self, j)
class MI6(Base2, B1):
def __init__(self, i):
Base2.__init__(self, i)
B1.__init__(self)
class B2(B1):
def v(self):
return 2
class B3(object):
def v(self):
return 3
class B4(B3, B2):
def v(self):
return 4
class MI7(B4, MI6):
def __init__(self, i):
B4.__init__(self)
MI6.__init__(self, i)
class MI8(MI6, B3):
def __init__(self, i):
MI6.__init__(self, i)
B3.__init__(self)
class MI8b(B3, MI6):
def __init__(self, i):
B3.__init__(self)
MI6.__init__(self, i)
mi1 = MI1(1, 2)
assert mi1.foo() == 1
assert mi1.bar() == 2
mi2 = MI2(3, 4)
assert mi2.v() == 1
assert mi2.foo() == 3
assert mi2.bar() == 4
mi3 = MI3(5, 6)
assert mi3.v() == 1
assert mi3.foo() == 5
assert mi3.bar() == 6
mi4 = MI4(7, 8, 9)
assert mi4.v() == 1
assert mi4.foo() == 8
assert mi4.bar() == 7
mi5 = MI5(10, 11)
assert mi5.v() == 1
assert mi5.foo() == 10
assert mi5.bar() == 11
mi6 = MI6(12)
assert mi6.v() == 1
assert mi6.bar() == 12
mi7 = MI7(13)
assert mi7.v() == 4
assert mi7.bar() == 13
mi8 = MI8(14)
assert mi8.v() == 1
assert mi8.bar() == 14
mi8b = MI8b(15)
assert mi8b.v() == 3
assert mi8b.bar() == 15
def test_multiple_inheritance_python_many_bases():
from pybind11_tests import (BaseN1, BaseN2, BaseN3, BaseN4, BaseN5, BaseN6, BaseN7,
BaseN8, BaseN9, BaseN10, BaseN11, BaseN12, BaseN13, BaseN14,
BaseN15, BaseN16, BaseN17)
class MIMany14(BaseN1, BaseN2, BaseN3, BaseN4):
def __init__(self):
BaseN1.__init__(self, 1)
BaseN2.__init__(self, 2)
BaseN3.__init__(self, 3)
BaseN4.__init__(self, 4)
class MIMany58(BaseN5, BaseN6, BaseN7, BaseN8):
def __init__(self):
BaseN5.__init__(self, 5)
BaseN6.__init__(self, 6)
BaseN7.__init__(self, 7)
BaseN8.__init__(self, 8)
class MIMany916(BaseN9, BaseN10, BaseN11, BaseN12, BaseN13, BaseN14, BaseN15, BaseN16):
def __init__(self):
BaseN9.__init__(self, 9)
BaseN10.__init__(self, 10)
BaseN11.__init__(self, 11)
BaseN12.__init__(self, 12)
BaseN13.__init__(self, 13)
BaseN14.__init__(self, 14)
BaseN15.__init__(self, 15)
BaseN16.__init__(self, 16)
class MIMany19(MIMany14, MIMany58, BaseN9):
def __init__(self):
MIMany14.__init__(self)
MIMany58.__init__(self)
BaseN9.__init__(self, 9)
class MIMany117(MIMany14, MIMany58, MIMany916, BaseN17):
def __init__(self):
MIMany14.__init__(self)
MIMany58.__init__(self)
MIMany916.__init__(self)
BaseN17.__init__(self, 17)
# Inherits from 4 registered C++ classes: can fit in one pointer on any modern arch:
a = MIMany14()
for i in range(1, 4):
assert getattr(a, "f" + str(i))() == 2 * i
# Inherits from 8: requires 1/2 pointers worth of holder flags on 32/64-bit arch:
b = MIMany916()
for i in range(9, 16):
assert getattr(b, "f" + str(i))() == 2 * i
# Inherits from 9: requires >= 2 pointers worth of holder flags
c = MIMany19()
for i in range(1, 9):
assert getattr(c, "f" + str(i))() == 2 * i
# Inherits from 17: requires >= 3 pointers worth of holder flags
d = MIMany117()
for i in range(1, 17):
assert getattr(d, "f" + str(i))() == 2 * i
def test_multiple_inheritance_virtbase(): def test_multiple_inheritance_virtbase():
......
...@@ -81,6 +81,28 @@ private: ...@@ -81,6 +81,28 @@ private:
} }
}; };
/// This is just a wrapper around unique_ptr, but with extra fields to deliberately bloat up the
/// holder size to trigger the non-simple-layout internal instance layout for single inheritance with
/// large holder type.
template <typename T> class huge_unique_ptr {
std::unique_ptr<T> ptr;
uint64_t padding[10];
public:
huge_unique_ptr(T *p) : ptr(p) {};
T *get() { return ptr.get(); }
};
class MyObject5 { // managed by huge_unique_ptr
public:
MyObject5(int value) : value{value} {
print_created(this);
}
int value;
~MyObject5() {
print_destroyed(this);
}
};
/// Make pybind aware of the ref-counted wrapper type (s) /// Make pybind aware of the ref-counted wrapper type (s)
// ref<T> is a wrapper for 'Object' which uses intrusive reference counting // ref<T> is a wrapper for 'Object' which uses intrusive reference counting
...@@ -89,6 +111,7 @@ private: ...@@ -89,6 +111,7 @@ private:
PYBIND11_DECLARE_HOLDER_TYPE(T, ref<T>, true); PYBIND11_DECLARE_HOLDER_TYPE(T, ref<T>, true);
PYBIND11_DECLARE_HOLDER_TYPE(T, std::shared_ptr<T>); // Not required any more for std::shared_ptr, PYBIND11_DECLARE_HOLDER_TYPE(T, std::shared_ptr<T>); // Not required any more for std::shared_ptr,
// but it should compile without error // but it should compile without error
PYBIND11_DECLARE_HOLDER_TYPE(T, huge_unique_ptr<T>);
// Make pybind11 aware of the non-standard getter member function // Make pybind11 aware of the non-standard getter member function
namespace pybind11 { namespace detail { namespace pybind11 { namespace detail {
...@@ -184,6 +207,10 @@ test_initializer smart_ptr([](py::module &m) { ...@@ -184,6 +207,10 @@ test_initializer smart_ptr([](py::module &m) {
.def(py::init<int>()) .def(py::init<int>())
.def_readwrite("value", &MyObject4::value); .def_readwrite("value", &MyObject4::value);
py::class_<MyObject5, huge_unique_ptr<MyObject5>>(m, "MyObject5")
.def(py::init<int>())
.def_readwrite("value", &MyObject5::value);
py::implicitly_convertible<py::int_, MyObject1>(); py::implicitly_convertible<py::int_, MyObject1>();
// Expose constructor stats for the ref type // Expose constructor stats for the ref type
......
...@@ -132,6 +132,16 @@ def test_unique_nodelete(): ...@@ -132,6 +132,16 @@ def test_unique_nodelete():
assert cstats.alive() == 1 # Leak, but that's intentional assert cstats.alive() == 1 # Leak, but that's intentional
def test_large_holder():
from pybind11_tests import MyObject5
o = MyObject5(5)
assert o.value == 5
cstats = ConstructorStats.get(MyObject5)
assert cstats.alive() == 1
del o
assert cstats.alive() == 0
def test_shared_ptr_and_references(): def test_shared_ptr_and_references():
from pybind11_tests.smart_ptr import SharedPtrRef, A from pybind11_tests.smart_ptr import SharedPtrRef, A
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment