mirror of
https://github.com/pybind/pybind11.git
synced 2024-11-25 06:35:12 +00:00
Merge pull request #403 from jagerman/alias-initialization
Implement py::init_alias<>() constructors
This commit is contained in:
commit
1f2e417d8c
@ -480,6 +480,36 @@ can now create a python class that inherits from ``Dog``:
|
|||||||
See the file :file:`tests/test_virtual_functions.cpp` for complete examples
|
See the file :file:`tests/test_virtual_functions.cpp` for complete examples
|
||||||
using both the duplication and templated trampoline approaches.
|
using both the duplication and templated trampoline approaches.
|
||||||
|
|
||||||
|
Extended trampoline class functionality
|
||||||
|
=======================================
|
||||||
|
|
||||||
|
The trampoline classes described in the previous sections are, by default, only
|
||||||
|
initialized when needed. More specifically, they are initialized when a python
|
||||||
|
class actually inherits from a registered type (instead of merely creating an
|
||||||
|
instance of the registered type), or when a registered constructor is only
|
||||||
|
valid for the trampoline class but not the registered class. This is primarily
|
||||||
|
for performance reasons: when the trampoline class is not needed for anything
|
||||||
|
except virtual method dispatching, not initializing the trampoline class
|
||||||
|
improves performance by avoiding needing to do a run-time check to see if the
|
||||||
|
inheriting python instance has an overloaded method.
|
||||||
|
|
||||||
|
Sometimes, however, it is useful to always initialize a trampoline class as an
|
||||||
|
intermediate class that does more than just handle virtual method dispatching.
|
||||||
|
For example, such a class might perform extra class initialization, extra
|
||||||
|
destruction operations, and might define new members and methods to enable a
|
||||||
|
more python-like interface to a class.
|
||||||
|
|
||||||
|
In order to tell pybind11 that it should *always* initialize the trampoline
|
||||||
|
class when creating new instances of a type, the class constructors should be
|
||||||
|
declared using ``py::init_alias<Args, ...>()`` instead of the usual
|
||||||
|
``py::init<Args, ...>()``. This forces construction via the trampoline class,
|
||||||
|
ensuring member initialization and (eventual) destruction.
|
||||||
|
|
||||||
|
.. seealso::
|
||||||
|
|
||||||
|
See the file :file:`tests/test_alias_initialization.cpp` for complete examples
|
||||||
|
showing both normal and forced trampoline instantiation.
|
||||||
|
|
||||||
.. _macro_notes:
|
.. _macro_notes:
|
||||||
|
|
||||||
General notes regarding convenience macros
|
General notes regarding convenience macros
|
||||||
|
@ -1112,17 +1112,17 @@ private:
|
|||||||
|
|
||||||
NAMESPACE_BEGIN(detail)
|
NAMESPACE_BEGIN(detail)
|
||||||
template <typename... Args> struct init {
|
template <typename... Args> struct init {
|
||||||
template <typename Class, typename... Extra, typename std::enable_if<!Class::has_alias, int>::type = 0>
|
template <typename Class, typename... Extra, enable_if_t<!Class::has_alias, int> = 0>
|
||||||
void execute(Class &cl, const Extra&... extra) const {
|
static void execute(Class &cl, const Extra&... extra) {
|
||||||
using Base = typename Class::type;
|
using Base = typename Class::type;
|
||||||
/// Function which calls a specific C++ in-place constructor
|
/// Function which calls a specific C++ in-place constructor
|
||||||
cl.def("__init__", [](Base *self_, Args... args) { new (self_) Base(args...); }, extra...);
|
cl.def("__init__", [](Base *self_, Args... args) { new (self_) Base(args...); }, extra...);
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename Class, typename... Extra,
|
template <typename Class, typename... Extra,
|
||||||
typename std::enable_if<Class::has_alias &&
|
enable_if_t<Class::has_alias &&
|
||||||
std::is_constructible<typename Class::type, Args...>::value, int>::type = 0>
|
std::is_constructible<typename Class::type, Args...>::value, int> = 0>
|
||||||
void execute(Class &cl, const Extra&... extra) const {
|
static void execute(Class &cl, const Extra&... extra) {
|
||||||
using Base = typename Class::type;
|
using Base = typename Class::type;
|
||||||
using Alias = typename Class::type_alias;
|
using Alias = typename Class::type_alias;
|
||||||
handle cl_type = cl;
|
handle cl_type = cl;
|
||||||
@ -1135,14 +1135,22 @@ template <typename... Args> struct init {
|
|||||||
}
|
}
|
||||||
|
|
||||||
template <typename Class, typename... Extra,
|
template <typename Class, typename... Extra,
|
||||||
typename std::enable_if<Class::has_alias &&
|
enable_if_t<Class::has_alias &&
|
||||||
!std::is_constructible<typename Class::type, Args...>::value, int>::type = 0>
|
!std::is_constructible<typename Class::type, Args...>::value, int> = 0>
|
||||||
void execute(Class &cl, const Extra&... extra) const {
|
static void execute(Class &cl, const Extra&... extra) {
|
||||||
|
init_alias<Args...>::execute(cl, extra...);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
template <typename... Args> struct init_alias {
|
||||||
|
template <typename Class, typename... Extra,
|
||||||
|
enable_if_t<Class::has_alias && std::is_constructible<typename Class::type_alias, Args...>::value, int> = 0>
|
||||||
|
static void execute(Class &cl, const Extra&... extra) {
|
||||||
using Alias = typename Class::type_alias;
|
using Alias = typename Class::type_alias;
|
||||||
cl.def("__init__", [](Alias *self_, Args... args) { new (self_) Alias(args...); }, extra...);
|
cl.def("__init__", [](Alias *self_, Args... args) { new (self_) Alias(args...); }, extra...);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
inline void keep_alive_impl(handle nurse, handle patient) {
|
inline void keep_alive_impl(handle nurse, handle patient) {
|
||||||
/* Clever approach based on weak references taken from Boost.Python */
|
/* Clever approach based on weak references taken from Boost.Python */
|
||||||
if (!nurse || !patient)
|
if (!nurse || !patient)
|
||||||
@ -1177,6 +1185,7 @@ struct iterator_state {
|
|||||||
NAMESPACE_END(detail)
|
NAMESPACE_END(detail)
|
||||||
|
|
||||||
template <typename... Args> detail::init<Args...> init() { return detail::init<Args...>(); }
|
template <typename... Args> detail::init<Args...> init() { return detail::init<Args...>(); }
|
||||||
|
template <typename... Args> detail::init_alias<Args...> init_alias() { return detail::init_alias<Args...>(); }
|
||||||
|
|
||||||
template <typename Iterator,
|
template <typename Iterator,
|
||||||
typename Sentinel,
|
typename Sentinel,
|
||||||
|
@ -4,6 +4,7 @@ if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
|
|||||||
endif()
|
endif()
|
||||||
|
|
||||||
set(PYBIND11_TEST_FILES
|
set(PYBIND11_TEST_FILES
|
||||||
|
test_alias_initialization.cpp
|
||||||
test_buffers.cpp
|
test_buffers.cpp
|
||||||
test_callbacks.cpp
|
test_callbacks.cpp
|
||||||
test_class_args.cpp
|
test_class_args.cpp
|
||||||
|
62
tests/test_alias_initialization.cpp
Normal file
62
tests/test_alias_initialization.cpp
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
/*
|
||||||
|
tests/test_alias_initialization.cpp -- test cases and example of different trampoline
|
||||||
|
initialization modes
|
||||||
|
|
||||||
|
Copyright (c) 2016 Wenzel Jakob <wenzel.jakob@epfl.ch>, Jason Rhinelander <jason@imaginary.ca>
|
||||||
|
|
||||||
|
All rights reserved. Use of this source code is governed by a
|
||||||
|
BSD-style license that can be found in the LICENSE file.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "pybind11_tests.h"
|
||||||
|
|
||||||
|
test_initializer alias_initialization([](py::module &m) {
|
||||||
|
// don't invoke Python dispatch classes by default when instantiating C++ classes that were not
|
||||||
|
// extended on the Python side
|
||||||
|
struct A {
|
||||||
|
virtual ~A() {}
|
||||||
|
virtual void f() { py::print("A.f()"); }
|
||||||
|
};
|
||||||
|
|
||||||
|
struct PyA : A {
|
||||||
|
PyA() { py::print("PyA.PyA()"); }
|
||||||
|
~PyA() { py::print("PyA.~PyA()"); }
|
||||||
|
|
||||||
|
void f() override {
|
||||||
|
py::print("PyA.f()");
|
||||||
|
PYBIND11_OVERLOAD(void, A, f);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
auto call_f = [](A *a) { a->f(); };
|
||||||
|
|
||||||
|
py::class_<A, PyA>(m, "A")
|
||||||
|
.def(py::init<>())
|
||||||
|
.def("f", &A::f);
|
||||||
|
|
||||||
|
m.def("call_f", call_f);
|
||||||
|
|
||||||
|
|
||||||
|
// ... unless we explicitly request it, as in this example:
|
||||||
|
struct A2 {
|
||||||
|
virtual ~A2() {}
|
||||||
|
virtual void f() { py::print("A2.f()"); }
|
||||||
|
};
|
||||||
|
|
||||||
|
struct PyA2 : A2 {
|
||||||
|
PyA2() { py::print("PyA2.PyA2()"); }
|
||||||
|
~PyA2() { py::print("PyA2.~PyA2()"); }
|
||||||
|
void f() override {
|
||||||
|
py::print("PyA2.f()");
|
||||||
|
PYBIND11_OVERLOAD(void, A2, f);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
py::class_<A2, PyA2>(m, "A2")
|
||||||
|
.def(py::init_alias<>())
|
||||||
|
.def("f", &A2::f);
|
||||||
|
|
||||||
|
m.def("call_f", [](A2 *a2) { a2->f(); });
|
||||||
|
|
||||||
|
});
|
||||||
|
|
80
tests/test_alias_initialization.py
Normal file
80
tests/test_alias_initialization.py
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
import pytest
|
||||||
|
import gc
|
||||||
|
|
||||||
|
def test_alias_delay_initialization(capture, msg):
|
||||||
|
|
||||||
|
# A only initializes its trampoline class when we inherit from it; if we
|
||||||
|
# just create and use an A instance directly, the trampoline initialization
|
||||||
|
# is bypassed and we only initialize an A() instead (for performance
|
||||||
|
# reasons)
|
||||||
|
from pybind11_tests import A, call_f
|
||||||
|
|
||||||
|
class B(A):
|
||||||
|
def __init__(self):
|
||||||
|
super(B, self).__init__()
|
||||||
|
|
||||||
|
def f(self):
|
||||||
|
print("In python f()")
|
||||||
|
|
||||||
|
# C++ version
|
||||||
|
with capture:
|
||||||
|
a = A()
|
||||||
|
call_f(a)
|
||||||
|
del a
|
||||||
|
gc.collect()
|
||||||
|
assert capture == "A.f()"
|
||||||
|
|
||||||
|
# Python version
|
||||||
|
with capture:
|
||||||
|
b = B()
|
||||||
|
call_f(b)
|
||||||
|
del b
|
||||||
|
gc.collect()
|
||||||
|
assert capture == """
|
||||||
|
PyA.PyA()
|
||||||
|
PyA.f()
|
||||||
|
In python f()
|
||||||
|
PyA.~PyA()
|
||||||
|
"""
|
||||||
|
|
||||||
|
def test_alias_delay_initialization(capture, msg):
|
||||||
|
from pybind11_tests import A2, call_f
|
||||||
|
|
||||||
|
# A2, unlike the above, is configured to always initialize the alias; while
|
||||||
|
# the extra initialization and extra class layer has small virtual dispatch
|
||||||
|
# performance penalty, it also allows us to do more things with the
|
||||||
|
# trampoline class such as defining local variables and performing
|
||||||
|
# construction/destruction.
|
||||||
|
|
||||||
|
class B2(A2):
|
||||||
|
def __init__(self):
|
||||||
|
super(B2, self).__init__()
|
||||||
|
|
||||||
|
def f(self):
|
||||||
|
print("In python B2.f()")
|
||||||
|
|
||||||
|
# No python subclass version
|
||||||
|
with capture:
|
||||||
|
a2 = A2()
|
||||||
|
call_f(a2)
|
||||||
|
del a2
|
||||||
|
gc.collect()
|
||||||
|
assert capture == """
|
||||||
|
PyA2.PyA2()
|
||||||
|
PyA2.f()
|
||||||
|
A2.f()
|
||||||
|
PyA2.~PyA2()
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Python subclass version
|
||||||
|
with capture:
|
||||||
|
b2 = B2()
|
||||||
|
call_f(b2)
|
||||||
|
del b2
|
||||||
|
gc.collect()
|
||||||
|
assert capture == """
|
||||||
|
PyA2.PyA2()
|
||||||
|
PyA2.f()
|
||||||
|
In python B2.f()
|
||||||
|
PyA2.~PyA2()
|
||||||
|
"""
|
@ -134,30 +134,6 @@ void init_issues(py::module &m) {
|
|||||||
m2.def("expect_float", [](float f) { return f; });
|
m2.def("expect_float", [](float f) { return f; });
|
||||||
m2.def("expect_int", [](int i) { return i; });
|
m2.def("expect_int", [](int i) { return i; });
|
||||||
|
|
||||||
// (no id): don't invoke Python dispatch code when instantiating C++
|
|
||||||
// classes that were not extended on the Python side
|
|
||||||
struct A {
|
|
||||||
virtual ~A() {}
|
|
||||||
virtual void f() { py::print("A.f()"); }
|
|
||||||
};
|
|
||||||
|
|
||||||
struct PyA : A {
|
|
||||||
PyA() { py::print("PyA.PyA()"); }
|
|
||||||
|
|
||||||
void f() override {
|
|
||||||
py::print("PyA.f()");
|
|
||||||
PYBIND11_OVERLOAD(void, A, f);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
auto call_f = [](A *a) { a->f(); };
|
|
||||||
|
|
||||||
pybind11::class_<A, std::unique_ptr<A>, PyA>(m2, "A")
|
|
||||||
.def(py::init<>())
|
|
||||||
.def("f", &A::f);
|
|
||||||
|
|
||||||
m2.def("call_f", call_f);
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
py::class_<Placeholder>(m2, "Placeholder");
|
py::class_<Placeholder>(m2, "Placeholder");
|
||||||
throw std::logic_error("Expected an exception!");
|
throw std::logic_error("Expected an exception!");
|
||||||
|
@ -79,30 +79,6 @@ def test_no_id(capture, msg):
|
|||||||
"""
|
"""
|
||||||
assert expect_float(12) == 12
|
assert expect_float(12) == 12
|
||||||
|
|
||||||
from pybind11_tests.issues import A, call_f
|
|
||||||
|
|
||||||
class B(A):
|
|
||||||
def __init__(self):
|
|
||||||
super(B, self).__init__()
|
|
||||||
|
|
||||||
def f(self):
|
|
||||||
print("In python f()")
|
|
||||||
|
|
||||||
# C++ version
|
|
||||||
with capture:
|
|
||||||
a = A()
|
|
||||||
call_f(a)
|
|
||||||
assert capture == "A.f()"
|
|
||||||
|
|
||||||
# Python version
|
|
||||||
with capture:
|
|
||||||
b = B()
|
|
||||||
call_f(b)
|
|
||||||
assert capture == """
|
|
||||||
PyA.PyA()
|
|
||||||
PyA.f()
|
|
||||||
In python f()
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
def test_str_issue(msg):
|
def test_str_issue(msg):
|
||||||
|
Loading…
Reference in New Issue
Block a user