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
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.
......@@ -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;
......
......@@ -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); }
// 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();
/// 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
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;
/// If true, the pointer is owned which means we're free to manage it with a holder.
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
template <typename type, typename holder_type = std::unique_ptr<type>> struct instance : instance_essentials<type> {
holder_type holder;
};
static_assert(std::is_standard_layout<instance>::value, "Internal error: `pybind11::detail::instance` is not standard layout!");
struct overload_hash {
inline size_t operator()(const std::pair<const PyObject *, const char *>& v) const {
......@@ -372,23 +428,20 @@ struct overload_hash {
/// Internal data structure used to track registered instances and types
struct internals {
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_multimap<const void *, void*> registered_instances; // void * -> PyObject*
std::unordered_map<std::type_index, void*> registered_types_cpp; // std::type_index -> type_info
std::unordered_map<PyTypeObject *, std::vector<type_info *>> registered_types_py; // PyTypeObject* -> base type_info(s)
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_map<std::type_index, std::vector<bool (*)(PyObject *, void *&)>> direct_conversions;
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
PyTypeObject *static_property_type;
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)
decltype(PyThread_create_key()) tstate = 0; // Usually an int but a long on Cygwin64 with Python 3.x
PyInterpreterState *istate = nullptr;
#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
......
......@@ -293,7 +293,7 @@ protected:
if (!chain) {
/* No existing overload was found, create a new function object */
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_meth = reinterpret_cast<PyCFunction>(*dispatcher);
rec->def->ml_flags = METH_VARARGS | METH_KEYWORDS;
......@@ -707,10 +707,8 @@ protected:
return nullptr;
} else {
if (overloads->is_constructor) {
/* When a constructor ran successfully, the corresponding
holder type (e.g. std::unique_ptr) must still be initialized. */
auto tinfo = get_type_info(Py_TYPE(parent.ptr()));
tinfo->init_holder(parent.ptr(), nullptr);
auto tinfo = get_type_info((PyTypeObject *) overloads->scope.ptr());
tinfo->init_holder(reinterpret_cast<instance *>(parent.ptr()), nullptr);
}
return result.ptr();
}
......@@ -727,7 +725,7 @@ public:
if (!options::show_user_defined_docstrings()) doc = nullptr;
#if PY_MAJOR_VERSION >= 3
PyModuleDef *def = new PyModuleDef();
memset(def, 0, sizeof(PyModuleDef));
std::memset(def, 0, sizeof(PyModuleDef));
def->m_name = name;
def->m_doc = doc;
def->m_size = -1;
......@@ -830,6 +828,7 @@ protected:
tinfo->cpptype = rec.type;
tinfo->type_size = rec.type_size;
tinfo->operator_new = rec.operator_new;
tinfo->holder_size_in_ptrs = size_in_ptrs(rec.holder_size);
tinfo->init_holder = rec.init_holder;
tinfo->dealloc = rec.dealloc;
tinfo->simple_type = true;
......@@ -840,7 +839,7 @@ protected:
tinfo->direct_conversions = &internals.direct_conversions[tindex];
tinfo->default_holder = rec.default_holder;
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) {
mark_parents_nonsimple(tinfo->type);
......@@ -923,7 +922,6 @@ public:
using type_alias = detail::exactly_one_t<is_subtype, void, options...>;
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 instance_type = detail::instance<type, holder_type>;
static_assert(detail::all_of<is_valid_class_option<options>...>::value,
"Unknown/invalid class_ template parameters provided");
......@@ -947,7 +945,7 @@ public:
record.name = name;
record.type = &typeid(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.dealloc = dealloc;
record.default_holder = std::is_same<holder_type, std::unique_ptr<type>>::value;
......@@ -1139,53 +1137,57 @@ public:
private:
/// Initialize holder object, variant 1: object derives from enable_shared_from_this
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 {
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) {
new (&inst->holder) holder_type(std::move(sh));
inst->holder_constructed = true;
new (&v_h.holder<holder_type>()) holder_type(std::move(sh));
v_h.set_holder_constructed();
}
} catch (const std::bad_weak_ptr &) {}
if (!inst->holder_constructed && inst->owned) {
new (&inst->holder) holder_type(inst->value);
inst->holder_constructed = true;
if (!v_h.holder_constructed() && inst->owned) {
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,
std::true_type /*is_copy_constructible*/) {
new (&inst->holder) holder_type(*holder_ptr);
static void init_holder_from_existing(const detail::value_and_holder &v_h,
const holder_type *holder_ptr, std::true_type /*is_copy_constructible*/) {
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,
std::false_type /*is_copy_constructible*/) {
new (&inst->holder) holder_type(std::move(*const_cast<holder_type *>(holder_ptr)));
static void init_holder_from_existing(const detail::value_and_holder &v_h,
const holder_type *holder_ptr, std::false_type /*is_copy_constructible*/) {
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
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) {
init_holder_from_existing(inst, holder_ptr, std::is_copy_constructible<holder_type>());
inst->holder_constructed = true;
init_holder_from_existing(v_h, holder_ptr, std::is_copy_constructible<holder_type>());
v_h.set_holder_constructed();
} else if (inst->owned || detail::always_construct_holder<holder_type>::value) {
new (&inst->holder) holder_type(inst->value);
inst->holder_constructed = true;
new (&v_h.holder<holder_type>()) holder_type(v_h.value_ptr<type>());
v_h.set_holder_constructed();
}
}
/// Initialize holder object of an instance, possibly given a pointer to an existing holder
static void init_holder(PyObject *inst_, const void *holder_ptr) {
auto inst = (instance_type *) inst_;
init_holder_helper(inst, (const holder_type *) holder_ptr, inst->value);
static void init_holder(detail::instance *inst, const void *holder_ptr) {
auto v_h = inst->get_value_and_holder(detail::get_type_info(typeid(type)));
init_holder_helper(inst, v_h, (const holder_type *) holder_ptr, v_h.value_ptr<type>());
}
static void dealloc(PyObject *inst_) {
instance_type *inst = (instance_type *) inst_;
if (inst->holder_constructed)
inst->holder.~holder_type();
else if (inst->owned)
detail::call_operator_delete(inst->value);
/// Deallocates an instance; via holder, if constructed; otherwise via operator delete.
static void dealloc(const detail::value_and_holder &v_h) {
if (v_h.holder_constructed())
v_h.holder<holder_type>().~holder_type();
else
detail::call_operator_delete(v_h.value_ptr<type>());
}
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
);
}
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>
struct iterator_state {
Iterator it;
......
......@@ -169,7 +169,7 @@ public:
auto &internals = py::detail::get_internals();
const std::type_index *t1 = nullptr, *t2 = nullptr;
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) {
if (p.second == type_info) {
if (t1) {
......
......@@ -23,6 +23,11 @@ struct Base2 {
int i;
};
template <int N> struct BaseN {
BaseN(int i) : i(i) { }
int i;
};
struct Base12 : Base1, Base2 {
Base12(int i, int j) : Base1(i), Base2(j) { }
};
......@@ -45,6 +50,15 @@ test_initializer multiple_inheritance([](py::module &m) {
py::class_<MIType, Base12>(m, "MIType")
.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
// template parameters because pybind has to know the types involved; see discussion in #742 for
// details).
......
......@@ -53,15 +53,174 @@ def test_multiple_inheritance_mix2():
assert mt.bar() == 4
def test_multiple_inheritance_error():
"""Inheriting from multiple C++ bases in Python is not supported"""
def test_multiple_inheritance_python():
from pybind11_tests import Base1, Base2
with pytest.raises(TypeError) as excinfo:
# noinspection PyUnusedLocal
class MI(Base1, Base2):
pass
assert "Can't inherit from multiple C++ classes in Python" in str(excinfo.value)
class MI1(Base1, Base2):
def __init__(self, i, j):
Base1.__init__(self, i)
Base2.__init__(self, j)
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():
......
......@@ -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)
// ref<T> is a wrapper for 'Object' which uses intrusive reference counting
......@@ -89,6 +111,7 @@ private:
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,
// 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
namespace pybind11 { namespace detail {
......@@ -184,6 +207,10 @@ test_initializer smart_ptr([](py::module &m) {
.def(py::init<int>())
.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>();
// Expose constructor stats for the ref type
......
......@@ -132,6 +132,16 @@ def test_unique_nodelete():
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():
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