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
|
||||
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:
|
||||
|
||||
General notes regarding convenience macros
|
||||
|
@ -1112,17 +1112,17 @@ private:
|
||||
|
||||
NAMESPACE_BEGIN(detail)
|
||||
template <typename... Args> struct init {
|
||||
template <typename Class, typename... Extra, typename std::enable_if<!Class::has_alias, int>::type = 0>
|
||||
void execute(Class &cl, const Extra&... extra) const {
|
||||
template <typename Class, typename... Extra, enable_if_t<!Class::has_alias, int> = 0>
|
||||
static void execute(Class &cl, const Extra&... extra) {
|
||||
using Base = typename Class::type;
|
||||
/// Function which calls a specific C++ in-place constructor
|
||||
cl.def("__init__", [](Base *self_, Args... args) { new (self_) Base(args...); }, extra...);
|
||||
}
|
||||
|
||||
template <typename Class, typename... Extra,
|
||||
typename std::enable_if<Class::has_alias &&
|
||||
std::is_constructible<typename Class::type, Args...>::value, int>::type = 0>
|
||||
void execute(Class &cl, const Extra&... extra) const {
|
||||
enable_if_t<Class::has_alias &&
|
||||
std::is_constructible<typename Class::type, Args...>::value, int> = 0>
|
||||
static void execute(Class &cl, const Extra&... extra) {
|
||||
using Base = typename Class::type;
|
||||
using Alias = typename Class::type_alias;
|
||||
handle cl_type = cl;
|
||||
@ -1135,14 +1135,22 @@ template <typename... Args> struct init {
|
||||
}
|
||||
|
||||
template <typename Class, typename... Extra,
|
||||
typename std::enable_if<Class::has_alias &&
|
||||
!std::is_constructible<typename Class::type, Args...>::value, int>::type = 0>
|
||||
void execute(Class &cl, const Extra&... extra) const {
|
||||
enable_if_t<Class::has_alias &&
|
||||
!std::is_constructible<typename Class::type, Args...>::value, int> = 0>
|
||||
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;
|
||||
cl.def("__init__", [](Alias *self_, Args... args) { new (self_) Alias(args...); }, extra...);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
inline void keep_alive_impl(handle nurse, handle patient) {
|
||||
/* Clever approach based on weak references taken from Boost.Python */
|
||||
if (!nurse || !patient)
|
||||
@ -1177,6 +1185,7 @@ struct iterator_state {
|
||||
NAMESPACE_END(detail)
|
||||
|
||||
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,
|
||||
typename Sentinel,
|
||||
|
@ -4,6 +4,7 @@ if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
|
||||
endif()
|
||||
|
||||
set(PYBIND11_TEST_FILES
|
||||
test_alias_initialization.cpp
|
||||
test_buffers.cpp
|
||||
test_callbacks.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_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 {
|
||||
py::class_<Placeholder>(m2, "Placeholder");
|
||||
throw std::logic_error("Expected an exception!");
|
||||
|
@ -79,30 +79,6 @@ def test_no_id(capture, msg):
|
||||
"""
|
||||
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):
|
||||
|
Loading…
Reference in New Issue
Block a user