Skip to content

Commit c4e1800

Browse files
committed
Reimplement py::init<...> to use common factory code
This reimplements the py::init<...> implementations using the various functions added to support `py::init(...)`, and moves the implementing structs into `detail/init.h` from `pybind11.h`. It doesn't simply use a factory directly, as this is a very common case and implementation without an extra lambda call is a small but useful optimization. This, combined with the previous lazy initialization, also avoids needing placement new for `py::init<...>()` construction: such construction now occurs via an ordinary `new Type(...)`. A consequence of this is that it also fixes a potential bug when using multiple inheritance from Python: it was very easy to write classes that double-initialize an existing instance which had the potential to leak for non-pod classes. With the new implementation, an attempt to call `__init__` on an already-initialized object is now ignored. (This was already done in the previous commit for factory constructors). This change exposed a few warnings (fixed here) from deleting a pointer to a base class with virtual functions but without a virtual destructor. These look like legitimate warnings that we shouldn't suppress; this adds virtual destructors to the appropriate classes.
1 parent 464d989 commit c4e1800

8 files changed

+84
-54
lines changed

include/pybind11/attr.h

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -116,8 +116,6 @@ enum op_id : int;
116116
enum op_type : int;
117117
struct undefined_t;
118118
template <op_id id, op_type ot, typename L = undefined_t, typename R = undefined_t> struct op_;
119-
template <typename... Args> struct init;
120-
template <typename... Args> struct init_alias;
121119
inline void keep_alive_impl(size_t Nurse, size_t Patient, function_call &call, handle ret);
122120

123121
/// Internal data structure which holds metadata about a keyword argument

include/pybind11/detail/init.h

Lines changed: 57 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ PYBIND11_NOINLINE inline value_and_holder load_v_h(handle self_, type_info *tinf
4040
}
4141

4242

43-
// Implementing functions for py::init(...)
43+
// Implementing functions for all forms of py::init<...> and py::init(...)
4444
template <typename Class> using Cpp = typename Class::type;
4545
template <typename Class> using Alias = typename Class::type_alias;
4646
template <typename Class> using Holder = typename Class::holder_type;
@@ -161,6 +161,62 @@ void construct(value_and_holder &v_h, Alias<Class> &&result, bool) {
161161
deallocate(v_h) = new Alias<Class>(std::move(result));
162162
}
163163

164+
// Implementing class for py::init<...>()
165+
template <typename... Args> struct constructor {
166+
template <typename Class, typename... Extra, enable_if_t<!Class::has_alias, int> = 0>
167+
static void execute(Class &cl, const Extra&... extra) {
168+
auto *cl_type = get_type_info(typeid(Cpp<Class>));
169+
cl.def("__init__", [cl_type](handle self_, Args... args) {
170+
auto v_h = load_v_h(self_, cl_type);
171+
if (v_h.instance_registered()) return; // Ignore duplicate __init__ calls (see above)
172+
173+
construct<Class>(v_h, new Cpp<Class>(std::forward<Args>(args)...), false);
174+
}, extra...);
175+
}
176+
177+
template <typename Class, typename... Extra,
178+
enable_if_t<Class::has_alias &&
179+
std::is_constructible<Cpp<Class>, Args...>::value, int> = 0>
180+
static void execute(Class &cl, const Extra&... extra) {
181+
auto *cl_type = get_type_info(typeid(Cpp<Class>));
182+
cl.def("__init__", [cl_type](handle self_, Args... args) {
183+
auto v_h = load_v_h(self_, cl_type);
184+
if (v_h.instance_registered()) return; // Ignore duplicate __init__ calls (see above)
185+
186+
if (Py_TYPE(v_h.inst) == cl_type->type)
187+
construct<Class>(v_h, new Cpp<Class>(std::forward<Args>(args)...), false);
188+
else
189+
construct<Class>(v_h, new Alias<Class>(std::forward<Args>(args)...), true);
190+
}, extra...);
191+
}
192+
193+
template <typename Class, typename... Extra,
194+
enable_if_t<Class::has_alias &&
195+
!std::is_constructible<Cpp<Class>, Args...>::value, int> = 0>
196+
static void execute(Class &cl, const Extra&... extra) {
197+
auto *cl_type = get_type_info(typeid(Cpp<Class>));
198+
cl.def("__init__", [cl_type](handle self_, Args... args) {
199+
auto v_h = load_v_h(self_, cl_type);
200+
if (v_h.instance_registered()) return; // Ignore duplicate __init__ calls (see above)
201+
construct<Class>(v_h, new Alias<Class>(std::forward<Args>(args)...), true);
202+
}, extra...);
203+
}
204+
};
205+
206+
// Implementing class for py::init_alias<...>()
207+
template <typename... Args> struct alias_constructor {
208+
template <typename Class, typename... Extra,
209+
enable_if_t<Class::has_alias && std::is_constructible<Alias<Class>, Args...>::value, int> = 0>
210+
static void execute(Class &cl, const Extra&... extra) {
211+
auto *cl_type = get_type_info(typeid(Cpp<Class>));
212+
cl.def("__init__", [cl_type](handle self_, Args... args) {
213+
auto v_h = load_v_h(self_, cl_type);
214+
if (v_h.instance_registered()) return; // Ignore duplicate __init__ calls (see above)
215+
construct<Class>(v_h, new Alias<Class>(std::forward<Args>(args)...), true);
216+
}, extra...);
217+
}
218+
};
219+
164220
// Implementation class for py::init(Func) and py::init(Func, AliasFunc)
165221
template <typename CFunc, typename AFuncIn, typename... Args> struct factory {
166222
private:

include/pybind11/pybind11.h

Lines changed: 4 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1046,13 +1046,13 @@ class class_ : public detail::generic_type {
10461046
}
10471047

10481048
template <typename... Args, typename... Extra>
1049-
class_ &def(const detail::init<Args...> &init, const Extra&... extra) {
1049+
class_ &def(const detail::initimpl::constructor<Args...> &init, const Extra&... extra) {
10501050
init.execute(*this, extra...);
10511051
return *this;
10521052
}
10531053

10541054
template <typename... Args, typename... Extra>
1055-
class_ &def(const detail::init_alias<Args...> &init, const Extra&... extra) {
1055+
class_ &def(const detail::initimpl::alias_constructor<Args...> &init, const Extra&... extra) {
10561056
init.execute(*this, extra...);
10571057
return *this;
10581058
}
@@ -1351,10 +1351,10 @@ template <typename Type> class enum_ : public class_<Type> {
13511351
};
13521352

13531353
/// Binds an existing constructor taking arguments Args...
1354-
template <typename... Args> detail::init<Args...> init() { return detail::init<Args...>(); }
1354+
template <typename... Args> detail::initimpl::constructor<Args...> init() { return {}; }
13551355
/// Like `init<Args...>()`, but the instance is always constructed through the alias class (even
13561356
/// when not inheriting on the Python side).
1357-
template <typename... Args> detail::init_alias<Args...> init_alias() { return detail::init_alias<Args...>(); }
1357+
template <typename... Args> detail::initimpl::alias_constructor<Args...> init_alias() { return {}; }
13581358

13591359
/// Binds a factory function as a constructor
13601360
template <typename Func, typename Ret = detail::initimpl::factory_t<Func>>
@@ -1368,44 +1368,6 @@ Ret init(CFunc &&c, AFunc &&a) {
13681368
}
13691369

13701370
NAMESPACE_BEGIN(detail)
1371-
template <typename... Args> struct init {
1372-
template <typename Class, typename... Extra, enable_if_t<!Class::has_alias, int> = 0>
1373-
static void execute(Class &cl, const Extra&... extra) {
1374-
using Base = typename Class::type;
1375-
/// Function which calls a specific C++ in-place constructor
1376-
cl.def("__init__", [](Base *self_, Args... args) { new (self_) Base(args...); }, extra...);
1377-
}
1378-
1379-
template <typename Class, typename... Extra,
1380-
enable_if_t<Class::has_alias &&
1381-
std::is_constructible<typename Class::type, Args...>::value, int> = 0>
1382-
static void execute(Class &cl, const Extra&... extra) {
1383-
using Base = typename Class::type;
1384-
using Alias = typename Class::type_alias;
1385-
handle cl_type = cl;
1386-
cl.def("__init__", [cl_type](handle self_, Args... args) {
1387-
if (self_.get_type().is(cl_type))
1388-
new (self_.cast<Base *>()) Base(args...);
1389-
else
1390-
new (self_.cast<Alias *>()) Alias(args...);
1391-
}, extra...);
1392-
}
1393-
1394-
template <typename Class, typename... Extra,
1395-
enable_if_t<Class::has_alias &&
1396-
!std::is_constructible<typename Class::type, Args...>::value, int> = 0>
1397-
static void execute(Class &cl, const Extra&... extra) {
1398-
init_alias<Args...>::execute(cl, extra...);
1399-
}
1400-
};
1401-
template <typename... Args> struct init_alias {
1402-
template <typename Class, typename... Extra,
1403-
enable_if_t<Class::has_alias && std::is_constructible<typename Class::type_alias, Args...>::value, int> = 0>
1404-
static void execute(Class &cl, const Extra&... extra) {
1405-
using Alias = typename Class::type_alias;
1406-
cl.def("__init__", [](Alias *self_, Args... args) { new (self_) Alias(args...); }, extra...);
1407-
}
1408-
};
14091371

14101372

14111373
inline void keep_alive_impl(handle nurse, handle patient) {

tests/test_class.py

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -141,20 +141,17 @@ class SubAliased(m.AliasedHasOpNewDelSize):
141141
d = m.HasOpNewDelBoth()
142142
assert capture == """
143143
A new 8
144-
A placement-new 8
145144
B new 4
146-
B placement-new 4
147145
D new 32
148-
D placement-new 32
149146
"""
150147
sz_alias = str(m.AliasedHasOpNewDelSize.size_alias)
151148
sz_noalias = str(m.AliasedHasOpNewDelSize.size_noalias)
152149
with capture:
153150
c = m.AliasedHasOpNewDelSize()
154151
c2 = SubAliased()
155152
assert capture == (
156-
"C new " + sz_alias + "\nC placement-new " + sz_noalias + "\n" +
157-
"C new " + sz_alias + "\nC placement-new " + sz_alias + "\n"
153+
"C new " + sz_noalias + "\n" +
154+
"C new " + sz_alias + "\n"
158155
)
159156

160157
with capture:

tests/test_factory_constructors.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -274,8 +274,9 @@ TEST_SUBMODULE(factory_constructors, m) {
274274
}
275275
int i;
276276
};
277-
// Workaround for a `py::init<args>` on a class without placement new support
277+
// As of 2.2, `py::init<args>` no longer requires placement new
278278
py::class_<NoPlacementNew>(m, "NoPlacementNew")
279+
.def(py::init<int>())
279280
.def(py::init([]() { return new NoPlacementNew(100); }))
280281
.def_readwrite("i", &NoPlacementNew::i)
281282
;

tests/test_factory_constructors.py

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -284,7 +284,19 @@ def get(self):
284284

285285

286286
def test_no_placement_new(capture):
287-
"""Tests a workaround for `py::init<...>` with a class that doesn't support placement new."""
287+
"""Prior to 2.2, `py::init<...>` relied on the type supporting placement
288+
new; this tests a class without placement new support."""
289+
with capture:
290+
a = m.NoPlacementNew(123)
291+
292+
found = re.search(r'^operator new called, returning (\d+)\n$', str(capture))
293+
assert found
294+
assert a.i == 123
295+
with capture:
296+
del a
297+
pytest.gc_collect()
298+
assert capture == "operator delete called on " + found.group(1)
299+
288300
with capture:
289301
b = m.NoPlacementNew()
290302

tests/test_multiple_inheritance.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,8 @@ def __init__(self, i, j):
7373
class MI4(MI3, m.Base2):
7474
def __init__(self, i, j):
7575
MI3.__init__(self, i, j)
76-
# m.Base2 is already initialized (via MI2)
76+
# This should be ignored (Base2 is already initialized via MI2):
77+
m.Base2.__init__(self, i + 100)
7778

7879
class MI5(m.Base2, B1, m.Base1):
7980
def __init__(self, i, j):

tests/test_virtual_functions.cpp

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,7 @@ class NCVirtTrampoline : public NCVirt {
148148
struct Base {
149149
/* for some reason MSVC2015 can't compile this if the function is pure virtual */
150150
virtual std::string dispatch() const { return {}; };
151+
virtual ~Base() = default;
151152
};
152153

153154
struct DispatchIssue : Base {
@@ -259,6 +260,7 @@ TEST_SUBMODULE(virtual_functions, m) {
259260
virtual std::string &str_ref() { return v; }
260261
virtual A A_value() { return a; }
261262
virtual A &A_ref() { return a; }
263+
virtual ~OverrideTest() = default;
262264
};
263265

264266
class PyOverrideTest : public OverrideTest {
@@ -311,6 +313,7 @@ public: \
311313
return say_something(1) + " " + std::to_string(unlucky_number()); \
312314
}
313315
A_METHODS
316+
virtual ~A_Repeat() = default;
314317
};
315318
class B_Repeat : public A_Repeat {
316319
#define B_METHODS \
@@ -335,7 +338,7 @@ D_METHODS
335338
};
336339

337340
// Base classes for templated inheritance trampolines. Identical to the repeat-everything version:
338-
class A_Tpl { A_METHODS };
341+
class A_Tpl { A_METHODS; virtual ~A_Tpl() = default; };
339342
class B_Tpl : public A_Tpl { B_METHODS };
340343
class C_Tpl : public B_Tpl { C_METHODS };
341344
class D_Tpl : public C_Tpl { D_METHODS };

0 commit comments

Comments
 (0)