mirror of
https://github.com/pybind/pybind11.git
synced 2024-11-22 05:05:11 +00:00
Adding py::smart_holder (for smart-pointer interoperability). (#2672)
* Adding test_unique_ptr_member (for desired PyCLIF behavior). See also: https://github.com/pybind/pybind11/issues/2583 Does not build with upstream master or https://github.com/pybind/pybind11/pull/2047, but builds with https://github.com/RobotLocomotion/pybind11 and almost runs: ``` Running tests in directory "/usr/local/google/home/rwgk/forked/EricCousineau-TRI/pybind11/tests": ================================================================================= test session starts ================================================================================= platform linux -- Python 3.8.5, pytest-5.4.3, py-1.9.0, pluggy-0.13.1 rootdir: /usr/local/google/home/rwgk/forked/EricCousineau-TRI/pybind11/tests, inifile: pytest.ini collected 2 items test_unique_ptr_member.py .F [100%] ====================================================================================== FAILURES ======================================================================================= _____________________________________________________________________________ test_pointee_and_ptr_owner ______________________________________________________________________________ def test_pointee_and_ptr_owner(): obj = m.pointee() assert obj.get_int() == 213 m.ptr_owner(obj) with pytest.raises(ValueError) as exc_info: > obj.get_int() E Failed: DID NOT RAISE <class 'ValueError'> test_unique_ptr_member.py:17: Failed ============================================================================= 1 failed, 1 passed in 0.06s ============================================================================= ``` * unique_ptr or shared_ptr return * new test_variant_unique_shared with vptr_holder prototype * moving prototype code to pybind11/vptr_holder.h, adding type_caster specialization to make the bindings involving unique_ptr passing compile, but load and cast implementations are missing * disabling GitHub Actions on pull_request (for this PR) * disabling AppVeyor (for this PR) * TRIGGER_SEGSEV macro, annotations for GET_STACK (vptr::get), GET_INT_STACK (pointee) * adding test_promotion_of_disowned_to_shared * Copying tests as-is from xxx_value_ptr_xxx_holder branch. https://github.com/rwgk/pybind11/tree/xxx_value_ptr_xxx_holder Systematically exercising returning and passing unique_ptr<T>, shared_ptr<T> with unique_ptr, shared_ptr holder. Observations: test_holder_unique_ptr: make_unique_pointee OK pass_unique_pointee BUILD_FAIL (as documented) make_shared_pointee Abort free(): double free detected pass_shared_pointee RuntimeError: Unable to load a custom holder type from a default-holder instance test_holder_shared_ptr: make_unique_pointee Segmentation fault (#1138) pass_unique_pointee BUILD_FAIL (as documented) make_shared_pointee OK pass_shared_pointee OK * Copying tests as-is from xxx_value_ptr_xxx_holder branch. https://github.com/rwgk/pybind11/tree/xxx_value_ptr_xxx_holder Systematically exercising casting between shared_ptr<base>, shared_ptr<derived>. * Demonstration of Undefined Behavior in handling of shared_ptr holder. Based on https://godbolt.org/z/4fdjaW by jorgbrown@ (thanks Jorg!). * Additional demonstration of Undefined Behavior in handling of shared_ptr holder. * fixing up-down mixup in comment * Demonstration of Undefined Behavior in handling of polymorphic pointers. (This demo does NOT involve smart pointers at all, unlike the otherwise similar test_smart_ptr_private_first_base.) * minor test_private_first_base.cpp simplification (after discovering that this can be wrapped with Boost.Python, using boost::noncopyable) * pybind11 equivalent of Boost.Python test similar to reproducer under #1333 * Snapshot of WIP, TODO: shared_ptr deleter with on/off switch * Adding vptr_deleter. * Adding from/as unique_ptr<T> and unique_ptr<T, D>. * Adding from_shared_ptr. Some polishing. * New tests/core/smart_holder_poc_test.cpp, using Catch2. * Adding in vptr_deleter_guard_flag. * Improved labeling of TEST_CASEs. * Shuffling existing TEST_CASEs into systematic matrix. * Implementing all [S]uccess tests. * Implementing all [E]xception tests. * Testing of exceptions not covered by the from-as matrix. * Adding top-level comment. * Converting from methods to factory functions (no functional change). * Removing obsolete and very incomplete test (replaced by Catch2-based test). * Removing stray file. * Adding type_caster_bare_interface_demo. * Adding shared_ptr<mpty>, shared_ptr<mpty const> casters. * Adding unique_ptr<mpty>, unique_ptr<mpty const> casters. * Pure copy of `class class_` implementation in pybind11.h (master commit98f1bbb800
). * classh.h: renaming of class_ to classh + namespace; forking test_classh_wip from test_type_caster_bare_interface_demo. * Hard-coding smart_holder into classh. * Adding mpty::mtxt string member. * Adding isinstance<mpty> in type_caster::load functions. * Adding rvalue_ref, renaming const_value_ref to lvalue_ref & removing const. * Retrieving smart_holder pointer in type_caster<mpty>::load, and using it cast_op operators. * Factoring out smart_holder_type_caster_load. * Retrieving smart_holder pointer in type_caster<std::shared_ptr<mpty[ const]>>::load, and using it cast_op operators. * Improved error messaging: Cannot disown nullptr (as_unique_ptr). * Retrieving smart_holder pointer in type_caster<std::unique_ptr<mpty[ const]>>::load, and using it cast_op operators. * Pure `clang-format --style=file -i` change. * Pure `clang-format --style=file -i` change, with two `clang-format off` directives. * Fixing oversight (discovered by flake8). * flake8 cleanup * Systematically setting mtxt for all rtrn_mpty_* functions (preparation, the values are not actually used yet). * static cast handle for rtrn_cptr works by simply dropping in code from type_caster_base (marked with comments). * static cast handle for rtrn_cref works by simply dropping in code from type_caster_base (marked with comments). rtrn_mref and rtrn_mptr work via const_cast (to add const). * static cast handle for rtrn_valu works by simply dropping in code from type_caster_base (marked with comments). rtrn_rref raises a RuntimeError, to be investigated. * Copying type_caster_generic::cast into type_caster<mpty> as-is (preparation for handling smart pointers). * Pure clang-format change (applied to original type_caster_generic::cast). * Adding comment re potential use_count data race. * static handle cast implementations for rtrn_shmp, rtrn_shcp. * Adding MISSING comments in operator std::unique_ptr<mpty[ const]>. * static handle cast implementations for rtrn_uqmp, rtrn_uqcp. * Bug fix: vptr_deleter_armed_flag_ptr has to live on the heap. See new bullet point in comment section near the top. The variable was also renamed to reflect its function more accurately. * Fixing bugs discovered by ASAN. The code is now ASAN, MSAN, UBSAN clean. * Making test_type_caster_bare_interface_demo.cpp slightly more realistic, ASAN, MSAN, UBSAN clean. * Calling deregister_instance after disowning via unique_ptr. * Removing enable_shared_from_this stub, simplifying existing code, clang-format. Open question, with respect to the original code:76a160070b/include/pybind11/pybind11.h (L1510)
To me it looks like the exact situation marked as `std::shared_ptr<Good> gp1 = not_so_good.getptr();` here: https://en.cppreference.com/w/cpp/memory/enable_shared_from_this The comment there is: `// undefined behavior (until C++17) and std::bad_weak_ptr thrown (since C++17)` Does the existing code have UB pre C++17? I'll leave handling of enable_shared_from_this for later, as the need arises. * Cosmetical change around helper functions. * Using type_caster_base<mpty>::src_and_type directly, removing copy. Also renaming one cast to cast_const_raw_ptr, for clarity. * Fixing clang-format oversight. * Using factored-out make_constructor (PR #2798), removing duplicate code. * Inserting additional assert to ensure a returned unique_ptr is always a new Python instance. * Adding minor comment (change to internals needed to distinguish uninitialized/disowned in error message). * Factoring out find_existing_python_instance(). * Moving factored-out make_constructor to test_classh_wip.cpp, restoring previous version of cast.h. This is currently the most practical approach. See PR #2798 for background. * Copying classh type_casters from test_classh_wip.cpp UNMODIFIED, as a baseline for generalizing the code. * Using pybind11/detail/classh_type_casters.h from test_classh_wip.cpp. * Adding & using PYBIND11_CLASSH_TYPE_CASTERS define. * Adding test_classh_inheritance, currently failing (passes with class_). * Removing .clang-format before git rebase master (where the file was added). * Bringing back .clang-format, the previous rm was a bad idea. * Folding in modified_type_caster_generic_load_impl, just enough to pass test_class_wip. test_classh_inheritance is still failing, but with a different error: [RuntimeError: Incompatible type (as_raw_ptr_unowned).] * Minimal changes needed to pass test_classh_inheritance. * First pass adjusting try_implicit_casts and try_load_foreign_module_local to capture loaded_v_h, but untested and guarded with pybind11_failure("Untested"). This was done mainly to determine general feasibility. Note the TODO in pybind11.h, where type_caster_generic::local_load is currently hard-coded. test_classh_wip and test_classh_inheritance still pass, as before. * Decoupling generic_type from type_caster_generic. * Changes and tests covering classh_type_casters try_implicit_casts. * Minimal test covering classh_type_casters load_impl Case 2b. * Removing stray isinstance<T>(src): it interferes with the py::module_local feature. Adding missing #includes. * Tests for classh py::module_local() feature. * Pure renaming of function names in test_classh_inheritance, similar to the systematic approach used in test_class_wip. NO functional changes. * Pure renaming of function and variable names, for better generalization when convoluting with inheritance. NO functional changes. * Adopting systematic naming scheme from test_classh_wip. NO functional changes. * Moving const after type name, for functions that cover a systematic scheme. NO functional changes. * Adding smart_holder_type_caster_load::loaded_as_shared_ptr, currently bypassing smart_holder shared_ptr tracking completely, but the tests pass and are sanitizer clean. * Removing rtti_held from smart_holder. See updated comment. * Cleaning up loaded_as_raw_ptr_unowned, loaded_as_shared_ptr. * Factoring out convert_type and folding into loaded_as_unique_ptr. * Folding convert_type into lvalue_ref and rvalue_ref paths. Some smart_holder_type_caster_load cleanup. * Using unique_ptr in local_load to replace static variable. Also adding local_load_safety_guard. * Converting test_unique_ptr_member to using classh: fully working, ASAN, MSAN, UBSAN clean. * Removing debugging comments (GET_STACK, GET_INT_STACK). cast.h is identical to current master again, pybind11.h only has the generic_type::initialize(..., &type_caster_generic::local_load) change. * Purging obsolete pybind11/vptr_holder.h and associated test. * Moving several tests to github.com/rwgk/rwgk_tbx/tree/main/pybind11_testsa2c2f88174
These tests are from experimenting, and for demonstrating UB in pybind11 multiple inheritance handling ("first_base"), to be fixed later. * Adding py::smart_holder support to py::class_, purging py::classh completely. * Renaming files in include directory, creating pybind11/smart_holder.h. * Renaming all "classh" to "smart_holder" in pybind11/detail/smart_holder_type_casters.h. The user-facing macro is now PYBIND11_SMART_HOLDER_TYPE_CASTERS. * Systematically renaming tests to use "class_sh" in the name. * Renaming test_type_caster_bare_interface_demo to test_type_caster_bare_interface. * Renaming new tests/core subdirectory to tests/pure_cpp. * Adding new tests to CMake config, resetting CI config. * Changing CMake file so that test_class_sh_module_local.py actually runs. * clang-tidy fixes. * 32-bit compatibility. * Reusing type_caster_base make_copy_constructor, make_move_constructor with a trick. * CMake COMPARE NATURAL is not available with older versions. * Adding copyright notices to new header files. * Explicitly define copy/move constructors/assignments. * Adding new header files to tests/extra_python_package/test_files.py. * Adding tests/pure_cpp/CMakeLists.txt. * Making use of the new find_existing_python_instance() function factored out with PR #2822. * Moving define PYBIND11_SMART_HOLDER_TYPE_CASTERS(T) down in the file. NO functional changes. Preparation for follow-up work (to keep that diff smaller). * Reintroducing py::classh, this time as a simple alias for py::class_<U, py::smart_holder>. * Replacing detail::is_smart_holder<H> in cast.h with detail::is_smart_holder_type_caster<T>. Moving get_local_load_function_ptr, init_instance_for_type to smart_holder_type_caster_class_hooks. Expanding static_assert in py::type::handle_of<> to accommodate smart_holder_type_casters. * Fixing oversight. * Adding classu alias for class_<U, std::unique_ptr<U>>. * Giving up on idea to use legacy init_instance only if is_base_of<type_caster_generic, type_caster<T>. There are use cases in the wild that define both a custom type_caster and class_. * Removing test_type_caster_bare_interface, which was moved to the separate PR #2834. * Moving up is_smart_holder_type_caster, to also use in cast_is_temporary_value_reference. * Adding smart_holder_type_casters for unique_ptr with custom deleter. SEVERE CODE DUPLICATION. This commit is to establish a baseline for consolidating the unique_ptr code. * Unification of unique_ptr, unique_ptr_with_deleter code in smart_holder_poc.h. Leads to more fitting error messages. Enables use of unique_ptr<T, D> smart_holder_type_casters also for unique_ptr<T>. * Copying files as-is from branch test_unique_ptr_member (PR #2672). * Adding comment, simplifying naming, cmake addition. * Introducing PYBIND11_USE_SMART_HOLDER_AS_DEFAULT macro (tested only undefined; there are many errors with the macro defined). * Removing test_type_caster_bare_interface, which was moved to the separate PR #2834. * Fixing oversight introduced with commit95425f13d6
. * Setting record.default_holder correctly for PYBIND11_USE_SMART_HOLDER_AS_DEFAULT. With this test_class.cpp builds and even mostly runs, except `test_multiple_instances_with_same_pointer`, which segfaults because it is using a `unique_ptr` holder but `smart_holder` `type_caster`. Also adding `static_assert`s to generate build errors for such situations, but guarding with `#if 0` to first pivot to test_factory_constructors.cpp. * Fixing up cast.h and smart_holder.h after rebase. * Removing detail/smart_holder_type_casters.h in separate commit. * Commenting out const in def_buffer(... const). With this, test_buffers builds and runs with PYBIND11_USE_SMART_HOLDER_AS_DEFAULT. Explanation why the const needs to be removed, or fix elsewhere, is still needed, but left for later. * Adding test_class_sh_factory_constructors, reproducing test_factory_constructors failure. Using py::class_ in this commit, to be changed to py::classh for debugging. * Removing include/pybind11/detail/smart_holder_type_casters.h from CMakeLists.txt, test_files.py (since it does not exist in this branch). * Adding // DANGER ZONE reminders. * Converting as many py::class_ to py::classh as possible, not breaking tests. * Adding initimpl::construct() overloads, resulting in test_class_sh_factory_constructors feature parity for py::class_ and py::classh. * Adding enable_if !is_smart_holder_type_caster to existing initimpl::construct(). With this test_factory_constructors.cpp builds with PYBIND11_USE_SMART_HOLDER_AS_DEFAULT. * Disabling shared_ptr&, shared_ptr* tests when building with PYBIND11_USE_SMART_HOLDER_AS_DEFAULT for now, pending work on smart_holder_type_caster<shared_ptr>. * Factoring out struct and class definitions into anonymous namespace. Preparation for building with PYBIND11_USE_SMART_HOLDER_AS_DEFAULT. * Simplifying from_unique_ptr(): typename D = std::default_delete<T> is not needed. Factoring out is_std_default_delete<T>() for consistentcy between ensure_compatible_rtti_uqp_del() and from_unique_ptr(). * Introducing PYBIND11_SMART_POINTER_HOLDER_TYPE_CASTERS. Using it in test_smart_ptr.cpp. With this test_smart_ptr builds with PYBIND11_USE_SMART_HOLDER_AS_DEFAULT and all but one test run successfully. * Introducing 1. type_caster_for_class_, used in PYBIND11_MAKE_OPAQUE, and 2. default_holder_type, used in stl_bind.h. * Using __VA_ARGS__ in PYBIND11_SMART_POINTER_HOLDER_TYPE_CASTERS. * Replacing condense_for_macro with much simpler approach. * Softening static_assert, to only check specifically that smart_holder is not mixed with type_caster_base, and unique_ptr/shared_ptr holders are not mixed with smart_holder_type_casters. * Adding PYBIND11_SMART_POINTER_HOLDER_TYPE_CASTERS in test_class.cpp (with this all but one test succeed with PYBIND11_USE_SMART_HOLDER_AS_DEFAULT). * Adding remaining PYBIND11_SMART_POINTER_HOLDER_TYPE_CASTERS. static_assert for "necessary conditions" for both types of default holder, static_assert for "strict conditions" guarded by new PYBIND11_STRICT_ASSERTS_CLASS_HOLDER_VS_TYPE_CASTER_MIX. All tests build & run as before with unique_ptr as the default holder, all tests build for smart_holder as the default holder, even with the strict static_assert. * Introducing check_is_smart_holder_type_caster() function for runtime check, and reinterpreting record.default_holder as "uses_unique_ptr_holder". With this test_smart_ptr succeeds. (All 42 tests build, 35 tests succeed, 5 run but have some failures, 2 segfault.) * Bug fix: Adding have_value() to smart_holder_type_caster_load. With this test_builtin_casters succeeds. (All 42 tests build, 36 tests succeed, 5 run but have some failures, 1 segfault.) * Adding unowned_void_ptr_from_direct_conversion to modified_type_caster_generic_load_impl. This fixes the last remaining segfault (test_numpy_dtypes). New stats for all tests combined: 12 failed, 458 passed. * Adding "Lazy allocation for unallocated values" (for old-style __init__) into load_value_and_holder. Deferring destruction of disowned holder until clear_instance, to remain inspectable for "uninitialized" or "disowned" detection. New stats for all tests combined: 5 failed, 465 passed. * Changing std::shared_ptr pointer/reference to const pointer/reference. New stats for all tests combined: 4 failed, 466 passed. * Adding return_value_policy::move to permissible policies for unique_ptr returns. New stats for all tests combined: 3 failed, 467 passed. * Overlooked flake8 fixes. * Manipulating failing ConstructorStats test to pass, to be able to run all tests with ASAN. This version of the code is ASAN clean with unique_ptr or smart_holder as the default. This change needs to be reverted after adopting the existing move-only-if-refcount-is-1 logic used by type_caster_base. * Adding copy constructor and move constructor tracking to atyp. Preparation for a follow-up change in smart_holder_type_caster, to make this test sensitive to the changing behavior. [skip ci] * Removing `operator T&&() &&` from smart_holder_type_caster, for compatibility with the behavior of type_caster_base. Enables reverting 2 of 3 test manipulations applied under commit249df7cbdb
. The manipulation in test_factory_constructors.py is NOT reverted in this commit. [skip ci] * Fixing unfortunate editing mishap. This reverts the last remaining test manipulation in commit249df7cbdb
and makes all existing unit tests pass with smart_holder as default holder. * GitHub CI clang-tidy fixes. * Adding messages to terse `static_assert`s, for pre-C++17 compatibility. * Using @pytest.mark.parametrize to run each assert separately (to see all errors, not just the first). * Systematically removing _atyp from function names, to make the test code simpler. * Using re.match to accommodate variable number of intermediate MvCtor. * Also removing `operator T()` from smart_holder_type_caster, to fix gcc compilation errors. The only loss is pass_rref in test_class_sh_basic. * Systematically replacing `detail::enable_if_t<...smart_holder...>` with `typename std::enable_if<...smart_holder...>::type`. Attempt to work around MSVC 2015 issues, to be tested via GitHub CI. The idea for this change originates from this comment: https://github.com/pybind/pybind11/issues/1616#issuecomment-444536813 * Importing re before pytest after observing a PyPy CI flake when importing pytest first. * Copying MSVC 2015 compatibility change from branch pr2672_use_smart_holder_as_default. * Introducing is_smart_holder_type_caster_base_tag, to keep smart_holder code more disconnected. * Working around MSVC 2015 bug. * Expanding comment for MSVC 2015 workaround. * Systematically changing std::enable_if back to detail::enable_if_t, effectively reverting commit5d4b6890a3
. * Removing unused smart_holder_type_caster_load::loaded_as_rvalue_ref (it was an oversight that it was not removed with commit23036a45eb
). * Removing py::classu, because it does not seem useful enough. * Reverting commit6349531306
by un-commenting `const` in `def_buffer(...)`. To make this possible, `operator T const&` and `operator T const*` in `smart_holder_type_caster` need to be marked as `const` member functions. * Adding construct() overloads for constructing smart_holder from alias unique_ptr, shared_ptr returns. * Adding test_class_sh_factory_constructors.cpp to tests/CMakeLists.txt (fixes oversight, this should have been added long before). * Compatibility with old clang versions (clang 3.6, 3.7 C++11). * Cleaning up changes to existing unit tests. * Systematically adding SMART_HOLDER_WIP tag. Removing minor UNTESTED tags (only the throw are not actually exercised, investing time there has a high cost but very little benefit). * Splitting out smart_holder_type_casters again, into new detail/smart_holder_type_casters_inline_include.h. * Splitting out smart_holder_init_inline_include.h. * Adding additional new include files to CMakeLists.txt, tests/extra_python_package/test_files.py. * clang-format cleanup of most smart_holder code. * Adding source code comments in response to review. * Simple micro-benchmark ("ubench") comparing runtime performance for several holders. Tested using github.com/rwgk/pybind11_scons and Google-internal build system. Sorry, no cmake support at the moment. First results: https://docs.google.com/spreadsheets/d/1InapCYws2Gt-stmFf_Bwl33eOMo3aLE_gc9adveY7RU/edit#gid=0 * Breaking out number_bucket.h, adding hook for also collecting performance data for PyCLIF. * Accounting for ubench in MANIFEST.in (simply prune, for now). * Smarter determination of call_repetitions. [skip ci] * Also scaling performance data to PyCLIF. [skip ci] * Adding ubench/python/number_bucket.clif here for general visibility. * Fix after rebase * Merging detail/smart_holder_init_inline_include.h into detail/init.h. * Renaming detail/is_smart_holder_type_caster.h -> detail/smart_holder_sfinae_hooks_only.h. * Renaming is_smart_holder_type_caster -> type_uses_smart_holder_type_caster for clarity. * Renaming type_caster_type_is_smart_holder_type_caster -> wrapped_type_uses_smart_holder_type_caster for clarity. * Renaming is_smart_holder_type_caster_base_tag -> smart_holder_type_caster_base_tag for simplicity. * Adding copyright notices and minor colateral cleanup. * iwyu cleanup (comprehensive only for cast.h and smart_holder*.h files). * Fixing `git rebase master` accident. * Moving large `pragma warning` block from pybind11.h to detail/common.h. * Fixing another `git rebase master` accident.
This commit is contained in:
parent
74a767d429
commit
1bafd5db5f
@ -105,6 +105,9 @@ set(PYBIND11_HEADERS
|
||||
include/pybind11/detail/descr.h
|
||||
include/pybind11/detail/init.h
|
||||
include/pybind11/detail/internals.h
|
||||
include/pybind11/detail/smart_holder_poc.h
|
||||
include/pybind11/detail/smart_holder_sfinae_hooks_only.h
|
||||
include/pybind11/detail/smart_holder_type_casters.h
|
||||
include/pybind11/detail/type_caster_base.h
|
||||
include/pybind11/detail/typeid.h
|
||||
include/pybind11/attr.h
|
||||
@ -124,6 +127,7 @@ set(PYBIND11_HEADERS
|
||||
include/pybind11/operators.h
|
||||
include/pybind11/pybind11.h
|
||||
include/pybind11/pytypes.h
|
||||
include/pybind11/smart_holder.h
|
||||
include/pybind11/stl.h
|
||||
include/pybind11/stl_bind.h)
|
||||
|
||||
|
@ -4,3 +4,4 @@ recursive-include pybind11 py.typed
|
||||
recursive-include pybind11 *.pyi
|
||||
include pybind11/share/cmake/pybind11/*.cmake
|
||||
include LICENSE README.rst pyproject.toml setup.py setup.cfg
|
||||
prune ubench
|
||||
|
@ -1,3 +1,4 @@
|
||||
// clang-format off
|
||||
/*
|
||||
pybind11/cast.h: Partial template specializations to cast between
|
||||
C++ and Python types
|
||||
@ -13,6 +14,7 @@
|
||||
#include "pytypes.h"
|
||||
#include "detail/common.h"
|
||||
#include "detail/descr.h"
|
||||
#include "detail/smart_holder_sfinae_hooks_only.h"
|
||||
#include "detail/type_caster_base.h"
|
||||
#include "detail/typeid.h"
|
||||
#include <array>
|
||||
@ -27,6 +29,10 @@
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#ifdef PYBIND11_USE_SMART_HOLDER_AS_DEFAULT
|
||||
#include "detail/smart_holder_type_casters.h"
|
||||
#endif
|
||||
|
||||
#if defined(PYBIND11_CPP17)
|
||||
# if defined(__has_include)
|
||||
# if __has_include(<string_view>)
|
||||
@ -47,8 +53,24 @@
|
||||
PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE)
|
||||
PYBIND11_NAMESPACE_BEGIN(detail)
|
||||
|
||||
template <typename type, typename SFINAE = void> class type_caster : public type_caster_base<type> { };
|
||||
template <typename type> using make_caster = type_caster<intrinsic_t<type>>;
|
||||
// clang-format on
|
||||
#ifndef PYBIND11_USE_SMART_HOLDER_AS_DEFAULT
|
||||
template <typename T>
|
||||
class type_caster_for_class_ : public type_caster_base<T> {};
|
||||
#endif
|
||||
|
||||
template <typename type, typename SFINAE = void>
|
||||
class type_caster : public type_caster_for_class_<type> {};
|
||||
|
||||
template <typename type>
|
||||
using make_caster = type_caster<intrinsic_t<type>>;
|
||||
|
||||
template <typename T>
|
||||
struct type_uses_smart_holder_type_caster {
|
||||
static constexpr bool value
|
||||
= std::is_base_of<smart_holder_type_caster_base_tag, make_caster<T>>::value;
|
||||
};
|
||||
// clang-format off
|
||||
|
||||
// Shortcut for calling a caster's `cast_op_type` cast operator for casting a type_caster to a T
|
||||
template <typename T> typename make_caster<T>::template cast_op_type<T> cast_op(make_caster<T> &caster) {
|
||||
@ -696,9 +718,11 @@ protected:
|
||||
holder_type holder;
|
||||
};
|
||||
|
||||
#ifndef PYBIND11_USE_SMART_HOLDER_AS_DEFAULT
|
||||
/// Specialize for the common std::shared_ptr, so users don't need to
|
||||
template <typename T>
|
||||
class type_caster<std::shared_ptr<T>> : public copyable_holder_caster<T, std::shared_ptr<T>> { };
|
||||
#endif
|
||||
|
||||
/// Type caster for holder types like std::unique_ptr.
|
||||
/// Please consider the SFINAE hook an implementation detail, as explained
|
||||
@ -715,9 +739,11 @@ struct move_only_holder_caster {
|
||||
static constexpr auto name = type_caster_base<type>::name;
|
||||
};
|
||||
|
||||
#ifndef PYBIND11_USE_SMART_HOLDER_AS_DEFAULT
|
||||
template <typename type, typename deleter>
|
||||
class type_caster<std::unique_ptr<type, deleter>>
|
||||
: public move_only_holder_caster<type, std::unique_ptr<type, deleter>> { };
|
||||
#endif
|
||||
|
||||
template <typename type, typename holder_type>
|
||||
using type_caster_holder = conditional_t<is_copy_constructible<holder_type>::value,
|
||||
@ -820,6 +846,7 @@ template <typename T> using move_never = none_of<move_always<T>, move_if_unrefer
|
||||
template <typename type> using cast_is_temporary_value_reference = bool_constant<
|
||||
(std::is_reference<type>::value || std::is_pointer<type>::value) &&
|
||||
!std::is_base_of<type_caster_generic, make_caster<type>>::value &&
|
||||
!type_uses_smart_holder_type_caster<intrinsic_t<type>>::value &&
|
||||
!std::is_same<intrinsic_t<type>, void>::value
|
||||
>;
|
||||
|
||||
@ -1363,9 +1390,9 @@ PYBIND11_NAMESPACE_END(detail)
|
||||
template<typename T>
|
||||
handle type::handle_of() {
|
||||
static_assert(
|
||||
std::is_base_of<detail::type_caster_generic, detail::make_caster<T>>::value,
|
||||
"py::type::of<T> only supports the case where T is a registered C++ types."
|
||||
);
|
||||
detail::any_of<std::is_base_of<detail::type_caster_generic, detail::make_caster<T>>,
|
||||
detail::type_uses_smart_holder_type_caster<T>>::value,
|
||||
"py::type::of<T> only supports the case where T is a registered C++ types.");
|
||||
|
||||
return detail::get_type_handle(typeid(T), true);
|
||||
}
|
||||
@ -1373,7 +1400,7 @@ handle type::handle_of() {
|
||||
|
||||
#define PYBIND11_MAKE_OPAQUE(...) \
|
||||
namespace pybind11 { namespace detail { \
|
||||
template<> class type_caster<__VA_ARGS__> : public type_caster_base<__VA_ARGS__> { }; \
|
||||
template<> class type_caster<__VA_ARGS__> : public type_caster_for_class_<__VA_ARGS__> { }; \
|
||||
}}
|
||||
|
||||
/// Lets you pass a type containing a `,` through a macro parameter without needing a separate
|
||||
|
@ -398,6 +398,8 @@ inline void clear_instance(PyObject *self) {
|
||||
|
||||
if (instance->owned || v_h.holder_constructed())
|
||||
v_h.type->dealloc(v_h);
|
||||
} else if (v_h.holder_constructed()) {
|
||||
v_h.type->dealloc(v_h); // Disowned instance.
|
||||
}
|
||||
}
|
||||
// Deallocate the value/holder layout internals:
|
||||
|
@ -13,6 +13,38 @@
|
||||
#define PYBIND11_VERSION_MINOR 6
|
||||
#define PYBIND11_VERSION_PATCH 3.dev1
|
||||
|
||||
#if defined(__INTEL_COMPILER)
|
||||
# pragma warning push
|
||||
# pragma warning disable 68 // integer conversion resulted in a change of sign
|
||||
# pragma warning disable 186 // pointless comparison of unsigned integer with zero
|
||||
# pragma warning disable 878 // incompatible exception specifications
|
||||
# pragma warning disable 1334 // the "template" keyword used for syntactic disambiguation may only be used within a template
|
||||
# pragma warning disable 1682 // implicit conversion of a 64-bit integral type to a smaller integral type (potential portability problem)
|
||||
# pragma warning disable 1786 // function "strdup" was declared deprecated
|
||||
# pragma warning disable 1875 // offsetof applied to non-POD (Plain Old Data) types is nonstandard
|
||||
# pragma warning disable 2196 // warning #2196: routine is both "inline" and "noinline"
|
||||
#elif defined(_MSC_VER)
|
||||
# pragma warning(push)
|
||||
# pragma warning(disable: 4100) // warning C4100: Unreferenced formal parameter
|
||||
# pragma warning(disable: 4127) // warning C4127: Conditional expression is constant
|
||||
# pragma warning(disable: 4512) // warning C4512: Assignment operator was implicitly defined as deleted
|
||||
# pragma warning(disable: 4800) // warning C4800: 'int': forcing value to bool 'true' or 'false' (performance warning)
|
||||
# pragma warning(disable: 4996) // warning C4996: The POSIX name for this item is deprecated. Instead, use the ISO C and C++ conformant name
|
||||
# pragma warning(disable: 4702) // warning C4702: unreachable code
|
||||
# pragma warning(disable: 4522) // warning C4522: multiple assignment operators specified
|
||||
# pragma warning(disable: 4505) // warning C4505: 'PySlice_GetIndicesEx': unreferenced local function has been removed (PyPy only)
|
||||
#elif defined(__GNUG__) && !defined(__clang__)
|
||||
# pragma GCC diagnostic push
|
||||
# pragma GCC diagnostic ignored "-Wunused-but-set-parameter"
|
||||
# pragma GCC diagnostic ignored "-Wunused-but-set-variable"
|
||||
# pragma GCC diagnostic ignored "-Wmissing-field-initializers"
|
||||
# pragma GCC diagnostic ignored "-Wstrict-aliasing"
|
||||
# pragma GCC diagnostic ignored "-Wattributes"
|
||||
# if __GNUC__ >= 7
|
||||
# pragma GCC diagnostic ignored "-Wnoexcept-type"
|
||||
# endif
|
||||
#endif
|
||||
|
||||
#define PYBIND11_NAMESPACE_BEGIN(name) namespace name {
|
||||
#define PYBIND11_NAMESPACE_END(name) }
|
||||
|
||||
|
@ -1,3 +1,4 @@
|
||||
// clang-format off
|
||||
/*
|
||||
pybind11/detail/init.h: init factory function implementation and support code.
|
||||
|
||||
@ -10,6 +11,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "class.h"
|
||||
#include "smart_holder_sfinae_hooks_only.h"
|
||||
|
||||
PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE)
|
||||
PYBIND11_NAMESPACE_BEGIN(detail)
|
||||
@ -105,11 +107,13 @@ void construct(value_and_holder &v_h, Cpp<Class> *ptr, bool need_alias) {
|
||||
// the holder and destruction happens when we leave the C++ scope, and the holder
|
||||
// class gets to handle the destruction however it likes.
|
||||
v_h.value_ptr() = ptr;
|
||||
v_h.set_instance_registered(true); // To prevent init_instance from registering it
|
||||
v_h.set_instance_registered(true); // SHORTCUT To prevent init_instance from registering it
|
||||
// DANGER ZONE BEGIN: exceptions will leave v_h in an invalid state.
|
||||
v_h.type->init_instance(v_h.inst, nullptr); // Set up the holder
|
||||
Holder<Class> temp_holder(std::move(v_h.holder<Holder<Class>>())); // Steal the holder
|
||||
v_h.type->dealloc(v_h); // Destroys the moved-out holder remains, resets value ptr to null
|
||||
v_h.set_instance_registered(false);
|
||||
// DANGER ZONE END.
|
||||
|
||||
construct_alias_from_cpp<Class>(is_alias_constructible<Class>{}, v_h, std::move(*ptr));
|
||||
} else {
|
||||
@ -129,7 +133,8 @@ void construct(value_and_holder &v_h, Alias<Class> *alias_ptr, bool) {
|
||||
// Holder return: copy its pointer, and move or copy the returned holder into the new instance's
|
||||
// holder. This also handles types like std::shared_ptr<T> and std::unique_ptr<T> where T is a
|
||||
// derived type (through those holder's implicit conversion from derived class holder constructors).
|
||||
template <typename Class>
|
||||
template <typename Class,
|
||||
detail::enable_if_t<!detail::type_uses_smart_holder_type_caster<Cpp<Class>>::value, int> = 0>
|
||||
void construct(value_and_holder &v_h, Holder<Class> holder, bool need_alias) {
|
||||
auto *ptr = holder_helper<Holder<Class>>::get(holder);
|
||||
no_nullptr(ptr);
|
||||
@ -166,6 +171,66 @@ void construct(value_and_holder &v_h, Alias<Class> &&result, bool) {
|
||||
v_h.value_ptr() = new Alias<Class>(std::move(result));
|
||||
}
|
||||
|
||||
// clang-format on
|
||||
template <
|
||||
typename Class,
|
||||
typename D = std::default_delete<Cpp<Class>>,
|
||||
detail::enable_if_t<detail::type_uses_smart_holder_type_caster<Cpp<Class>>::value, int> = 0>
|
||||
void construct(value_and_holder &v_h, std::unique_ptr<Cpp<Class>, D> &&unq_ptr, bool need_alias) {
|
||||
auto *ptr = unq_ptr.get();
|
||||
no_nullptr(ptr);
|
||||
if (Class::has_alias && need_alias)
|
||||
throw type_error("pybind11::init(): construction failed: returned std::unique_ptr pointee "
|
||||
"is not an alias instance");
|
||||
auto smhldr
|
||||
= type_caster<Cpp<Class>>::template smart_holder_from_unique_ptr(std::move(unq_ptr));
|
||||
v_h.value_ptr() = ptr;
|
||||
v_h.type->init_instance(v_h.inst, &smhldr);
|
||||
}
|
||||
|
||||
template <
|
||||
typename Class,
|
||||
typename D = std::default_delete<Alias<Class>>,
|
||||
detail::enable_if_t<detail::type_uses_smart_holder_type_caster<Alias<Class>>::value, int> = 0>
|
||||
void construct(value_and_holder &v_h,
|
||||
std::unique_ptr<Alias<Class>, D> &&unq_ptr,
|
||||
bool /*need_alias*/) {
|
||||
auto *ptr = unq_ptr.get();
|
||||
no_nullptr(ptr);
|
||||
auto smhldr
|
||||
= type_caster<Alias<Class>>::template smart_holder_from_unique_ptr(std::move(unq_ptr));
|
||||
v_h.value_ptr() = ptr;
|
||||
v_h.type->init_instance(v_h.inst, &smhldr);
|
||||
}
|
||||
|
||||
template <
|
||||
typename Class,
|
||||
detail::enable_if_t<detail::type_uses_smart_holder_type_caster<Cpp<Class>>::value, int> = 0>
|
||||
void construct(value_and_holder &v_h, std::shared_ptr<Cpp<Class>> &&shd_ptr, bool need_alias) {
|
||||
auto *ptr = shd_ptr.get();
|
||||
no_nullptr(ptr);
|
||||
if (Class::has_alias && need_alias)
|
||||
throw type_error("pybind11::init(): construction failed: returned std::shared_ptr pointee "
|
||||
"is not an alias instance");
|
||||
auto smhldr = type_caster<Cpp<Class>>::template smart_holder_from_shared_ptr(shd_ptr);
|
||||
v_h.value_ptr() = ptr;
|
||||
v_h.type->init_instance(v_h.inst, &smhldr);
|
||||
}
|
||||
|
||||
template <
|
||||
typename Class,
|
||||
detail::enable_if_t<detail::type_uses_smart_holder_type_caster<Alias<Class>>::value, int> = 0>
|
||||
void construct(value_and_holder &v_h,
|
||||
std::shared_ptr<Alias<Class>> &&shd_ptr,
|
||||
bool /*need_alias*/) {
|
||||
auto *ptr = shd_ptr.get();
|
||||
no_nullptr(ptr);
|
||||
auto smhldr = type_caster<Alias<Class>>::template smart_holder_from_shared_ptr(shd_ptr);
|
||||
v_h.value_ptr() = ptr;
|
||||
v_h.type->init_instance(v_h.inst, &smhldr);
|
||||
}
|
||||
// clang-format off
|
||||
|
||||
// Implementing class for py::init<...>()
|
||||
template <typename... Args>
|
||||
struct constructor {
|
||||
|
266
include/pybind11/detail/smart_holder_poc.h
Normal file
266
include/pybind11/detail/smart_holder_poc.h
Normal file
@ -0,0 +1,266 @@
|
||||
// Copyright (c) 2020-2021 The Pybind Development Team.
|
||||
// All rights reserved. Use of this source code is governed by a
|
||||
// BSD-style license that can be found in the LICENSE file.
|
||||
|
||||
/* Proof-of-Concept for smart pointer interoperability.
|
||||
|
||||
High-level aspects:
|
||||
|
||||
* Support all `unique_ptr`, `shared_ptr` interops that are feasible.
|
||||
|
||||
* Cleanly and clearly report all interops that are infeasible.
|
||||
|
||||
* Meant to fit into a `PyObject`, as a holder for C++ objects.
|
||||
|
||||
* Support a system design that makes it impossible to trigger
|
||||
C++ Undefined Behavior, especially from Python.
|
||||
|
||||
* Support a system design with clean runtime inheritance casting. From this
|
||||
it follows that the `smart_holder` needs to be type-erased (`void*`).
|
||||
|
||||
* Handling of RTTI for the type-erased held pointer is NOT implemented here.
|
||||
It is the responsibility of the caller to ensure that `static_cast<T *>`
|
||||
is well-formed when calling `as_*` member functions. Inheritance casting
|
||||
needs to be handled in a different layer (similar to the code organization
|
||||
in boost/python/object/inheritance.hpp).
|
||||
|
||||
Details:
|
||||
|
||||
* The "root holder" chosen here is a `shared_ptr<void>` (named `vptr` in this
|
||||
implementation). This choice is practically inevitable because `shared_ptr`
|
||||
has only very limited support for inspecting and accessing its deleter.
|
||||
|
||||
* If created from a raw pointer, or a `unique_ptr` without a custom deleter,
|
||||
`vptr` always uses a custom deleter, to support `unique_ptr`-like disowning.
|
||||
The custom deleters can be extended to included life-time managment for
|
||||
external objects (e.g. `PyObject`).
|
||||
|
||||
* If created from an external `shared_ptr`, or a `unique_ptr` with a custom
|
||||
deleter, including life-time management for external objects is infeasible.
|
||||
|
||||
* The smart_holder is movable but not copyable, as a consequence of using
|
||||
unique_ptr for the vptr_deleter_armed_flag_ptr. Note that the bool for
|
||||
the flag has to live on the heap, for the smart_holder to be movable.
|
||||
unique_ptr is a great fit for this situation.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <typeinfo>
|
||||
|
||||
// pybindit = Python Bindings Innovation Track.
|
||||
// Currently not in pybind11 namespace to signal that this POC does not depend
|
||||
// on any existing pybind11 functionality.
|
||||
namespace pybindit {
|
||||
namespace memory {
|
||||
|
||||
template <typename T>
|
||||
struct guarded_builtin_delete {
|
||||
bool *flag_ptr;
|
||||
explicit guarded_builtin_delete(bool *armed_flag_ptr) : flag_ptr{armed_flag_ptr} {}
|
||||
void operator()(T *raw_ptr) {
|
||||
if (*flag_ptr)
|
||||
delete raw_ptr;
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T, typename D>
|
||||
struct guarded_custom_deleter {
|
||||
bool *flag_ptr;
|
||||
explicit guarded_custom_deleter(bool *armed_flag_ptr) : flag_ptr{armed_flag_ptr} {}
|
||||
void operator()(T *raw_ptr) {
|
||||
if (*flag_ptr)
|
||||
D()(raw_ptr);
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
inline bool is_std_default_delete(const std::type_info &rtti_deleter) {
|
||||
return rtti_deleter == typeid(std::default_delete<T>)
|
||||
|| rtti_deleter == typeid(std::default_delete<T const>);
|
||||
}
|
||||
|
||||
struct smart_holder {
|
||||
const std::type_info *rtti_uqp_del;
|
||||
std::unique_ptr<bool> vptr_deleter_armed_flag_ptr;
|
||||
std::shared_ptr<void> vptr;
|
||||
bool vptr_is_using_noop_deleter : 1;
|
||||
bool vptr_is_using_builtin_delete : 1;
|
||||
bool vptr_is_external_shared_ptr : 1;
|
||||
bool is_populated : 1;
|
||||
|
||||
smart_holder()
|
||||
: rtti_uqp_del{nullptr}, vptr_is_using_noop_deleter{false},
|
||||
vptr_is_using_builtin_delete{false}, vptr_is_external_shared_ptr{false}, is_populated{
|
||||
false} {}
|
||||
|
||||
explicit smart_holder(bool vptr_deleter_armed_flag)
|
||||
: rtti_uqp_del{nullptr}, vptr_deleter_armed_flag_ptr{new bool{vptr_deleter_armed_flag}},
|
||||
vptr_is_using_noop_deleter{false}, vptr_is_using_builtin_delete{false},
|
||||
vptr_is_external_shared_ptr{false}, is_populated{false} {}
|
||||
|
||||
bool has_pointee() const { return vptr.get() != nullptr; }
|
||||
|
||||
void ensure_is_populated(const char *context) const {
|
||||
if (!is_populated) {
|
||||
throw std::runtime_error(std::string("Unpopulated holder (") + context + ").");
|
||||
}
|
||||
}
|
||||
|
||||
void ensure_vptr_is_using_builtin_delete(const char *context) const {
|
||||
if (vptr_is_external_shared_ptr) {
|
||||
throw std::runtime_error(std::string("Cannot disown external shared_ptr (") + context
|
||||
+ ").");
|
||||
}
|
||||
if (vptr_is_using_noop_deleter) {
|
||||
throw std::runtime_error(std::string("Cannot disown non-owning holder (") + context
|
||||
+ ").");
|
||||
}
|
||||
if (!vptr_is_using_builtin_delete) {
|
||||
throw std::runtime_error(std::string("Cannot disown custom deleter (") + context
|
||||
+ ").");
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T, typename D>
|
||||
void ensure_compatible_rtti_uqp_del(const char *context) const {
|
||||
const std::type_info *rtti_requested = &typeid(D);
|
||||
if (!rtti_uqp_del) {
|
||||
if (!is_std_default_delete<T>(*rtti_requested)) {
|
||||
throw std::runtime_error(std::string("Missing unique_ptr deleter (") + context
|
||||
+ ").");
|
||||
}
|
||||
ensure_vptr_is_using_builtin_delete(context);
|
||||
} else if (!(*rtti_requested == *rtti_uqp_del)) {
|
||||
throw std::runtime_error(std::string("Incompatible unique_ptr deleter (") + context
|
||||
+ ").");
|
||||
}
|
||||
}
|
||||
|
||||
void ensure_has_pointee(const char *context) const {
|
||||
if (!has_pointee()) {
|
||||
throw std::runtime_error(std::string("Disowned holder (") + context + ").");
|
||||
}
|
||||
}
|
||||
|
||||
void ensure_use_count_1(const char *context) const {
|
||||
if (vptr.get() == nullptr) {
|
||||
throw std::runtime_error(std::string("Cannot disown nullptr (") + context + ").");
|
||||
}
|
||||
// In multithreaded environments accessing use_count can lead to
|
||||
// race conditions, but in the context of Python it is a bug (elsewhere)
|
||||
// if the Global Interpreter Lock (GIL) is not being held when this code
|
||||
// is reached.
|
||||
// SMART_HOLDER_WIP: IMPROVABLE: assert(GIL is held).
|
||||
if (vptr.use_count() != 1) {
|
||||
throw std::runtime_error(std::string("Cannot disown use_count != 1 (") + context
|
||||
+ ").");
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
static smart_holder from_raw_ptr_unowned(T *raw_ptr) {
|
||||
smart_holder hld(false);
|
||||
hld.vptr.reset(raw_ptr, guarded_builtin_delete<T>(hld.vptr_deleter_armed_flag_ptr.get()));
|
||||
hld.vptr_is_using_noop_deleter = true;
|
||||
hld.is_populated = true;
|
||||
return hld;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
T *as_raw_ptr_unowned() const {
|
||||
return static_cast<T *>(vptr.get());
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
T &as_lvalue_ref() const {
|
||||
static const char *context = "as_lvalue_ref";
|
||||
ensure_is_populated(context);
|
||||
ensure_has_pointee(context);
|
||||
return *as_raw_ptr_unowned<T>();
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
T &&as_rvalue_ref() const {
|
||||
static const char *context = "as_rvalue_ref";
|
||||
ensure_is_populated(context);
|
||||
ensure_has_pointee(context);
|
||||
return std::move(*as_raw_ptr_unowned<T>());
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
static smart_holder from_raw_ptr_take_ownership(T *raw_ptr) {
|
||||
smart_holder hld(true);
|
||||
hld.vptr.reset(raw_ptr, guarded_builtin_delete<T>(hld.vptr_deleter_armed_flag_ptr.get()));
|
||||
hld.vptr_is_using_builtin_delete = true;
|
||||
hld.is_populated = true;
|
||||
return hld;
|
||||
}
|
||||
|
||||
void ensure_can_release_ownership(const char *context = "ensure_can_release_ownership") {
|
||||
ensure_vptr_is_using_builtin_delete(context);
|
||||
ensure_use_count_1(context);
|
||||
}
|
||||
|
||||
// Caller is responsible for calling ensure_can_release_ownership().
|
||||
void release_ownership() {
|
||||
*vptr_deleter_armed_flag_ptr = false;
|
||||
vptr.reset();
|
||||
vptr_deleter_armed_flag_ptr.reset();
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
T *as_raw_ptr_release_ownership(const char *context = "as_raw_ptr_release_ownership") {
|
||||
ensure_can_release_ownership(context);
|
||||
T *raw_ptr = as_raw_ptr_unowned<T>();
|
||||
release_ownership();
|
||||
return raw_ptr;
|
||||
}
|
||||
|
||||
template <typename T, typename D>
|
||||
static smart_holder from_unique_ptr(std::unique_ptr<T, D> &&unq_ptr) {
|
||||
smart_holder hld(true);
|
||||
hld.rtti_uqp_del = &typeid(D);
|
||||
hld.vptr_is_using_builtin_delete = is_std_default_delete<T>(*hld.rtti_uqp_del);
|
||||
if (hld.vptr_is_using_builtin_delete) {
|
||||
hld.vptr.reset(unq_ptr.get(),
|
||||
guarded_builtin_delete<T>(hld.vptr_deleter_armed_flag_ptr.get()));
|
||||
} else {
|
||||
hld.vptr.reset(unq_ptr.get(),
|
||||
guarded_custom_deleter<T, D>(hld.vptr_deleter_armed_flag_ptr.get()));
|
||||
}
|
||||
unq_ptr.release();
|
||||
hld.is_populated = true;
|
||||
return hld;
|
||||
}
|
||||
|
||||
template <typename T, typename D = std::default_delete<T>>
|
||||
std::unique_ptr<T, D> as_unique_ptr() {
|
||||
static const char *context = "as_unique_ptr";
|
||||
ensure_compatible_rtti_uqp_del<T, D>(context);
|
||||
ensure_use_count_1(context);
|
||||
T *raw_ptr = as_raw_ptr_unowned<T>();
|
||||
release_ownership();
|
||||
return std::unique_ptr<T, D>(raw_ptr);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
static smart_holder from_shared_ptr(std::shared_ptr<T> shd_ptr) {
|
||||
smart_holder hld;
|
||||
hld.vptr = std::static_pointer_cast<void>(shd_ptr);
|
||||
hld.vptr_is_external_shared_ptr = true;
|
||||
hld.is_populated = true;
|
||||
return hld;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
std::shared_ptr<T> as_shared_ptr() const {
|
||||
return std::static_pointer_cast<T>(vptr);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace memory
|
||||
} // namespace pybindit
|
33
include/pybind11/detail/smart_holder_sfinae_hooks_only.h
Normal file
33
include/pybind11/detail/smart_holder_sfinae_hooks_only.h
Normal file
@ -0,0 +1,33 @@
|
||||
// Copyright (c) 2021 The Pybind Development Team.
|
||||
// All rights reserved. Use of this source code is governed by a
|
||||
// BSD-style license that can be found in the LICENSE file.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "common.h"
|
||||
|
||||
#include <type_traits>
|
||||
|
||||
#ifndef PYBIND11_USE_SMART_HOLDER_AS_DEFAULT
|
||||
// #define PYBIND11_USE_SMART_HOLDER_AS_DEFAULT
|
||||
// Currently the main purpose of this switch is to enable non-intrusive comprehensive testing. If
|
||||
// and when `smart_holder` will actually become the released default is currently open. In the
|
||||
// meantime, the full functionality is easily available by using `py::classh`, which is just a
|
||||
// handy shortcut for `py::class_<T, py::smart_holder>` (see `pybind11/smart_holder.h`). Classes
|
||||
// wrapped in this way are fully compatible with everything existing.
|
||||
#endif
|
||||
|
||||
PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE)
|
||||
PYBIND11_NAMESPACE_BEGIN(detail)
|
||||
|
||||
template <typename T>
|
||||
struct is_smart_holder_type : std::false_type {};
|
||||
|
||||
// Tag to be used as base class, inspected by type_uses_smart_holder_type_caster<T> test.
|
||||
struct smart_holder_type_caster_base_tag {};
|
||||
|
||||
template <typename T>
|
||||
struct type_uses_smart_holder_type_caster;
|
||||
|
||||
PYBIND11_NAMESPACE_END(detail)
|
||||
PYBIND11_NAMESPACE_END(PYBIND11_NAMESPACE)
|
747
include/pybind11/detail/smart_holder_type_casters.h
Normal file
747
include/pybind11/detail/smart_holder_type_casters.h
Normal file
@ -0,0 +1,747 @@
|
||||
// Copyright (c) 2021 The Pybind Development Team.
|
||||
// All rights reserved. Use of this source code is governed by a
|
||||
// BSD-style license that can be found in the LICENSE file.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "../pytypes.h"
|
||||
#include "common.h"
|
||||
#include "descr.h"
|
||||
#include "internals.h"
|
||||
#include "smart_holder_poc.h"
|
||||
#include "smart_holder_sfinae_hooks_only.h"
|
||||
#include "type_caster_base.h"
|
||||
#include "typeid.h"
|
||||
|
||||
#include <cstddef>
|
||||
#include <memory>
|
||||
#include <new>
|
||||
#include <string>
|
||||
#include <type_traits>
|
||||
#include <typeinfo>
|
||||
#include <utility>
|
||||
|
||||
PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE)
|
||||
|
||||
using pybindit::memory::smart_holder;
|
||||
|
||||
PYBIND11_NAMESPACE_BEGIN(detail)
|
||||
|
||||
template <>
|
||||
struct is_smart_holder_type<smart_holder> : std::true_type {};
|
||||
|
||||
// SMART_HOLDER_WIP: Needs refactoring of existing pybind11 code.
|
||||
inline void register_instance(instance *self, void *valptr, const type_info *tinfo);
|
||||
inline bool deregister_instance(instance *self, void *valptr, const type_info *tinfo);
|
||||
|
||||
// The modified_type_caster_generic_load_impl could replace type_caster_generic::load_impl but not
|
||||
// vice versa. The main difference is that the original code only propagates a reference to the
|
||||
// held value, while the modified implementation propagates value_and_holder.
|
||||
// clang-format off
|
||||
class modified_type_caster_generic_load_impl {
|
||||
public:
|
||||
PYBIND11_NOINLINE modified_type_caster_generic_load_impl(const std::type_info &type_info)
|
||||
: typeinfo(get_type_info(type_info)), cpptype(&type_info) { }
|
||||
|
||||
explicit modified_type_caster_generic_load_impl(const type_info *typeinfo = nullptr)
|
||||
: typeinfo(typeinfo), cpptype(typeinfo ? typeinfo->cpptype : nullptr) { }
|
||||
|
||||
bool load(handle src, bool convert) {
|
||||
return load_impl<modified_type_caster_generic_load_impl>(src, convert);
|
||||
}
|
||||
|
||||
// Base methods for generic caster; there are overridden in copyable_holder_caster
|
||||
void load_value_and_holder(value_and_holder &&v_h) {
|
||||
if (!v_h.holder_constructed()) {
|
||||
// This is needed for old-style __init__.
|
||||
// type_caster_generic::load_value BEGIN
|
||||
auto *&vptr = v_h.value_ptr();
|
||||
// Lazy allocation for unallocated values:
|
||||
if (vptr == nullptr) {
|
||||
// Lazy allocation for unallocated values:
|
||||
auto *type = v_h.type ? v_h.type : typeinfo;
|
||||
if (type->operator_new) {
|
||||
vptr = type->operator_new(type->type_size);
|
||||
} else {
|
||||
#if defined(__cpp_aligned_new) && (!defined(_MSC_VER) || _MSC_VER >= 1912)
|
||||
if (type->type_align > __STDCPP_DEFAULT_NEW_ALIGNMENT__)
|
||||
vptr = ::operator new(type->type_size,
|
||||
std::align_val_t(type->type_align));
|
||||
else
|
||||
#endif
|
||||
vptr = ::operator new(type->type_size);
|
||||
}
|
||||
}
|
||||
// type_caster_generic::load_value END
|
||||
}
|
||||
loaded_v_h = std::move(v_h);
|
||||
loaded_v_h.type = typeinfo;
|
||||
}
|
||||
|
||||
bool try_implicit_casts(handle src, bool convert) {
|
||||
for (auto &cast : typeinfo->implicit_casts) {
|
||||
modified_type_caster_generic_load_impl sub_caster(*cast.first);
|
||||
if (sub_caster.load(src, convert)) {
|
||||
if (loaded_v_h_cpptype != nullptr) {
|
||||
pybind11_fail("smart_holder_type_casters: try_implicit_casts failure.");
|
||||
}
|
||||
loaded_v_h = sub_caster.loaded_v_h;
|
||||
loaded_v_h_cpptype = cast.first;
|
||||
implicit_cast = cast.second;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool try_direct_conversions(handle src) {
|
||||
for (auto &converter : *typeinfo->direct_conversions) {
|
||||
if (converter(src.ptr(), unowned_void_ptr_from_direct_conversion)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
PYBIND11_NOINLINE static void *local_load(PyObject *src, const type_info *ti) {
|
||||
std::unique_ptr<modified_type_caster_generic_load_impl> loader(
|
||||
new modified_type_caster_generic_load_impl(ti));
|
||||
if (loader->load(src, false)) {
|
||||
// Trick to work with the existing pybind11 internals.
|
||||
// The void pointer is immediately captured in a new unique_ptr in
|
||||
// try_load_foreign_module_local. If this assumption is violated sanitizers
|
||||
// will most likely flag a leak (verified to be the case with ASAN).
|
||||
return static_cast<void *>(loader.release());
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
/// Try to load with foreign typeinfo, if available. Used when there is no
|
||||
/// native typeinfo, or when the native one wasn't able to produce a value.
|
||||
PYBIND11_NOINLINE bool try_load_foreign_module_local(handle src) {
|
||||
constexpr auto *local_key = PYBIND11_MODULE_LOCAL_ID;
|
||||
const auto pytype = type::handle_of(src);
|
||||
if (!hasattr(pytype, local_key))
|
||||
return false;
|
||||
|
||||
type_info *foreign_typeinfo = reinterpret_borrow<capsule>(getattr(pytype, local_key));
|
||||
// Only consider this foreign loader if actually foreign and is a loader of the correct cpp type
|
||||
if (foreign_typeinfo->module_local_load == &local_load
|
||||
|| (cpptype && !same_type(*cpptype, *foreign_typeinfo->cpptype)))
|
||||
return false;
|
||||
|
||||
void* foreign_loader_void_ptr =
|
||||
foreign_typeinfo->module_local_load(src.ptr(), foreign_typeinfo);
|
||||
if (foreign_loader_void_ptr != nullptr) {
|
||||
auto foreign_loader = std::unique_ptr<modified_type_caster_generic_load_impl>(
|
||||
static_cast<modified_type_caster_generic_load_impl *>(foreign_loader_void_ptr));
|
||||
// Magic number intentionally hard-coded for simplicity and maximum robustness.
|
||||
if (foreign_loader->local_load_safety_guard != 1887406645) {
|
||||
pybind11_fail(
|
||||
"smart_holder_type_casters: Unexpected local_load_safety_guard,"
|
||||
" possibly due to py::class_ holder mixup.");
|
||||
}
|
||||
if (loaded_v_h_cpptype != nullptr) {
|
||||
pybind11_fail("smart_holder_type_casters: try_load_foreign_module_local failure.");
|
||||
}
|
||||
loaded_v_h = foreign_loader->loaded_v_h;
|
||||
loaded_v_h_cpptype = foreign_loader->loaded_v_h_cpptype;
|
||||
implicit_cast = foreign_loader->implicit_cast;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Implementation of `load`; this takes the type of `this` so that it can dispatch the relevant
|
||||
// bits of code between here and copyable_holder_caster where the two classes need different
|
||||
// logic (without having to resort to virtual inheritance).
|
||||
template <typename ThisT>
|
||||
PYBIND11_NOINLINE bool load_impl(handle src, bool convert) {
|
||||
if (!src) return false;
|
||||
if (!typeinfo) return try_load_foreign_module_local(src);
|
||||
if (src.is_none()) {
|
||||
// Defer accepting None to other overloads (if we aren't in convert mode):
|
||||
if (!convert) return false;
|
||||
loaded_v_h = value_and_holder();
|
||||
return true;
|
||||
}
|
||||
|
||||
auto &this_ = static_cast<ThisT &>(*this);
|
||||
|
||||
PyTypeObject *srctype = Py_TYPE(src.ptr());
|
||||
|
||||
// Case 1: If src is an exact type match for the target type then we can reinterpret_cast
|
||||
// the instance's value pointer to the target type:
|
||||
if (srctype == typeinfo->type) {
|
||||
this_.load_value_and_holder(reinterpret_cast<instance *>(src.ptr())->get_value_and_holder());
|
||||
return true;
|
||||
}
|
||||
// Case 2: We have a derived class
|
||||
else if (PyType_IsSubtype(srctype, typeinfo->type)) {
|
||||
auto &bases = all_type_info(srctype); // subtype bases
|
||||
bool no_cpp_mi = typeinfo->simple_type;
|
||||
|
||||
// Case 2a: the python type is a Python-inherited derived class that inherits from just
|
||||
// one simple (no MI) pybind11 class, or is an exact match, so the C++ instance is of
|
||||
// the right type and we can use reinterpret_cast.
|
||||
// (This is essentially the same as case 2b, but because not using multiple inheritance
|
||||
// is extremely common, we handle it specially to avoid the loop iterator and type
|
||||
// pointer lookup overhead)
|
||||
if (bases.size() == 1 && (no_cpp_mi || bases.front()->type == typeinfo->type)) {
|
||||
this_.load_value_and_holder(reinterpret_cast<instance *>(src.ptr())->get_value_and_holder());
|
||||
loaded_v_h_cpptype = bases.front()->cpptype;
|
||||
reinterpret_cast_deemed_ok = true;
|
||||
return true;
|
||||
}
|
||||
// Case 2b: the python type inherits from multiple C++ bases. Check the bases to see if
|
||||
// we can find an exact match (or, for a simple C++ type, an inherited match); if so, we
|
||||
// can safely reinterpret_cast to the relevant pointer.
|
||||
else if (bases.size() > 1) {
|
||||
for (auto base : bases) {
|
||||
if (no_cpp_mi ? PyType_IsSubtype(base->type, typeinfo->type) : base->type == typeinfo->type) {
|
||||
this_.load_value_and_holder(reinterpret_cast<instance *>(src.ptr())->get_value_and_holder(base));
|
||||
loaded_v_h_cpptype = base->cpptype;
|
||||
reinterpret_cast_deemed_ok = true;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Case 2c: C++ multiple inheritance is involved and we couldn't find an exact type match
|
||||
// in the registered bases, above, so try implicit casting (needed for proper C++ casting
|
||||
// when MI is involved).
|
||||
if (this_.try_implicit_casts(src, convert)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Perform an implicit conversion
|
||||
if (convert) {
|
||||
for (auto &converter : typeinfo->implicit_conversions) {
|
||||
auto temp = reinterpret_steal<object>(converter(src.ptr(), typeinfo->type));
|
||||
if (load_impl<ThisT>(temp, false)) {
|
||||
loader_life_support::add_patient(temp);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (this_.try_direct_conversions(src))
|
||||
return true;
|
||||
}
|
||||
|
||||
// Failed to match local typeinfo. Try again with global.
|
||||
if (typeinfo->module_local) {
|
||||
if (auto gtype = get_global_type_info(*typeinfo->cpptype)) {
|
||||
typeinfo = gtype;
|
||||
return load(src, false);
|
||||
}
|
||||
}
|
||||
|
||||
// Global typeinfo has precedence over foreign module_local
|
||||
return try_load_foreign_module_local(src);
|
||||
}
|
||||
|
||||
const type_info *typeinfo = nullptr;
|
||||
const std::type_info *cpptype = nullptr;
|
||||
void *unowned_void_ptr_from_direct_conversion = nullptr;
|
||||
const std::type_info *loaded_v_h_cpptype = nullptr;
|
||||
void *(*implicit_cast)(void *) = nullptr;
|
||||
value_and_holder loaded_v_h;
|
||||
bool reinterpret_cast_deemed_ok = false;
|
||||
// Magic number intentionally hard-coded, to guard against class_ holder mixups.
|
||||
// Ideally type_caster_generic would have a similar guard, but this requires a change there.
|
||||
// SMART_HOLDER_WIP: If it is decided that this guard is useful long term, potentially
|
||||
// set/reset this value in ctor/dtor, mark volatile.
|
||||
std::size_t local_load_safety_guard = 1887406645; // 32-bit compatible value for portability.
|
||||
};
|
||||
// clang-format on
|
||||
|
||||
struct smart_holder_type_caster_class_hooks : smart_holder_type_caster_base_tag {
|
||||
static decltype(&modified_type_caster_generic_load_impl::local_load)
|
||||
get_local_load_function_ptr() {
|
||||
return &modified_type_caster_generic_load_impl::local_load;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
static void init_instance_for_type(detail::instance *inst, const void *holder_const_void_ptr) {
|
||||
// Need for const_cast is a consequence of the type_info::init_instance type:
|
||||
// void (*init_instance)(instance *, const void *);
|
||||
auto holder_void_ptr = const_cast<void *>(holder_const_void_ptr);
|
||||
|
||||
auto v_h = inst->get_value_and_holder(detail::get_type_info(typeid(T)));
|
||||
if (!v_h.instance_registered()) {
|
||||
register_instance(inst, v_h.value_ptr(), v_h.type);
|
||||
v_h.set_instance_registered();
|
||||
}
|
||||
using holder_type = pybindit::memory::smart_holder;
|
||||
if (holder_void_ptr) {
|
||||
// Note: inst->owned ignored.
|
||||
auto holder_ptr = static_cast<holder_type *>(holder_void_ptr);
|
||||
new (std::addressof(v_h.holder<holder_type>())) holder_type(std::move(*holder_ptr));
|
||||
} else if (inst->owned) {
|
||||
new (std::addressof(v_h.holder<holder_type>()))
|
||||
holder_type(holder_type::from_raw_ptr_take_ownership(v_h.value_ptr<T>()));
|
||||
} else {
|
||||
new (std::addressof(v_h.holder<holder_type>()))
|
||||
holder_type(holder_type::from_raw_ptr_unowned(v_h.value_ptr<T>()));
|
||||
}
|
||||
v_h.set_holder_constructed();
|
||||
}
|
||||
|
||||
template <typename T, typename D>
|
||||
static smart_holder smart_holder_from_unique_ptr(std::unique_ptr<T, D> &&unq_ptr) {
|
||||
return pybindit::memory::smart_holder::from_unique_ptr(std::move(unq_ptr));
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
static smart_holder smart_holder_from_shared_ptr(std::shared_ptr<T> shd_ptr) {
|
||||
return pybindit::memory::smart_holder::from_shared_ptr(shd_ptr);
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
struct smart_holder_type_caster_load {
|
||||
using holder_type = pybindit::memory::smart_holder;
|
||||
|
||||
bool load(handle src, bool convert) {
|
||||
static_assert(type_uses_smart_holder_type_caster<T>::value, "Internal consistency error.");
|
||||
load_impl = modified_type_caster_generic_load_impl(typeid(T));
|
||||
if (!load_impl.load(src, convert))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
T *loaded_as_raw_ptr_unowned() const {
|
||||
void *void_ptr = load_impl.unowned_void_ptr_from_direct_conversion;
|
||||
if (void_ptr == nullptr) {
|
||||
if (have_holder()) {
|
||||
throw_if_uninitialized_or_disowned_holder();
|
||||
void_ptr = holder().template as_raw_ptr_unowned<void>();
|
||||
} else if (load_impl.loaded_v_h.vh != nullptr)
|
||||
void_ptr = load_impl.loaded_v_h.value_ptr();
|
||||
if (void_ptr == nullptr)
|
||||
return nullptr;
|
||||
}
|
||||
return convert_type(void_ptr);
|
||||
}
|
||||
|
||||
T &loaded_as_lvalue_ref() const {
|
||||
T *raw_ptr = loaded_as_raw_ptr_unowned();
|
||||
if (raw_ptr == nullptr)
|
||||
throw reference_cast_error();
|
||||
return *raw_ptr;
|
||||
}
|
||||
|
||||
std::shared_ptr<T> loaded_as_shared_ptr() const {
|
||||
if (load_impl.unowned_void_ptr_from_direct_conversion != nullptr)
|
||||
throw cast_error("Unowned pointer from direct conversion cannot be converted to a"
|
||||
" std::shared_ptr.");
|
||||
if (!have_holder())
|
||||
return nullptr;
|
||||
throw_if_uninitialized_or_disowned_holder();
|
||||
std::shared_ptr<void> void_ptr = holder().template as_shared_ptr<void>();
|
||||
return std::shared_ptr<T>(void_ptr, convert_type(void_ptr.get()));
|
||||
}
|
||||
|
||||
template <typename D>
|
||||
std::unique_ptr<T, D> loaded_as_unique_ptr(const char *context = "loaded_as_unique_ptr") {
|
||||
if (load_impl.unowned_void_ptr_from_direct_conversion != nullptr)
|
||||
throw cast_error("Unowned pointer from direct conversion cannot be converted to a"
|
||||
" std::unique_ptr.");
|
||||
if (!have_holder())
|
||||
return nullptr;
|
||||
throw_if_uninitialized_or_disowned_holder();
|
||||
holder().template ensure_compatible_rtti_uqp_del<T, D>(context);
|
||||
holder().ensure_use_count_1(context);
|
||||
auto raw_void_ptr = holder().template as_raw_ptr_unowned<void>();
|
||||
// SMART_HOLDER_WIP: MISSING: Safety checks for type conversions
|
||||
// (T must be polymorphic or meet certain other conditions).
|
||||
T *raw_type_ptr = convert_type(raw_void_ptr);
|
||||
|
||||
// Critical transfer-of-ownership section. This must stay together.
|
||||
holder().release_ownership();
|
||||
auto result = std::unique_ptr<T, D>(raw_type_ptr);
|
||||
|
||||
void *value_void_ptr = load_impl.loaded_v_h.value_ptr();
|
||||
if (value_void_ptr != raw_void_ptr) {
|
||||
pybind11_fail("smart_holder_type_casters: loaded_as_unique_ptr failure:"
|
||||
" value_void_ptr != raw_void_ptr");
|
||||
}
|
||||
load_impl.loaded_v_h.value_ptr() = nullptr;
|
||||
deregister_instance(load_impl.loaded_v_h.inst, value_void_ptr, load_impl.loaded_v_h.type);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private:
|
||||
modified_type_caster_generic_load_impl load_impl;
|
||||
|
||||
bool have_holder() const {
|
||||
return load_impl.loaded_v_h.vh != nullptr && load_impl.loaded_v_h.holder_constructed();
|
||||
}
|
||||
|
||||
holder_type &holder() const { return load_impl.loaded_v_h.holder<holder_type>(); }
|
||||
|
||||
// have_holder() must be true or this function will fail.
|
||||
void throw_if_uninitialized_or_disowned_holder() const {
|
||||
if (!holder().is_populated) {
|
||||
pybind11_fail("Missing value for wrapped C++ type:"
|
||||
" Python instance is uninitialized.");
|
||||
}
|
||||
if (!holder().has_pointee()) {
|
||||
throw cast_error("Missing value for wrapped C++ type:"
|
||||
" Python instance was disowned.");
|
||||
}
|
||||
}
|
||||
|
||||
T *convert_type(void *void_ptr) const {
|
||||
if (void_ptr != nullptr && load_impl.loaded_v_h_cpptype != nullptr
|
||||
&& !load_impl.reinterpret_cast_deemed_ok && load_impl.implicit_cast != nullptr) {
|
||||
void_ptr = load_impl.implicit_cast(void_ptr);
|
||||
}
|
||||
return static_cast<T *>(void_ptr);
|
||||
}
|
||||
};
|
||||
|
||||
// SMART_HOLDER_WIP: Needs refactoring of existing pybind11 code.
|
||||
struct make_constructor : private type_caster_base<int> { // Any type, nothing special about int.
|
||||
using type_caster_base<int>::Constructor;
|
||||
using type_caster_base<int>::make_copy_constructor;
|
||||
using type_caster_base<int>::make_move_constructor;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
struct smart_holder_type_caster : smart_holder_type_caster_load<T>,
|
||||
smart_holder_type_caster_class_hooks {
|
||||
static constexpr auto name = _<T>();
|
||||
|
||||
// static handle cast(T, ...)
|
||||
// is redundant (leads to ambiguous overloads).
|
||||
|
||||
static handle cast(T &&src, return_value_policy /*policy*/, handle parent) {
|
||||
// type_caster_base BEGIN
|
||||
// clang-format off
|
||||
return cast(&src, return_value_policy::move, parent);
|
||||
// clang-format on
|
||||
// type_caster_base END
|
||||
}
|
||||
|
||||
static handle cast(T const &src, return_value_policy policy, handle parent) {
|
||||
// type_caster_base BEGIN
|
||||
// clang-format off
|
||||
if (policy == return_value_policy::automatic || policy == return_value_policy::automatic_reference)
|
||||
policy = return_value_policy::copy;
|
||||
return cast(&src, policy, parent);
|
||||
// clang-format on
|
||||
// type_caster_base END
|
||||
}
|
||||
|
||||
static handle cast(T &src, return_value_policy policy, handle parent) {
|
||||
return cast(const_cast<T const &>(src), policy, parent); // Mutbl2Const
|
||||
}
|
||||
|
||||
static handle cast(T const *src, return_value_policy policy, handle parent) {
|
||||
auto st = type_caster_base<T>::src_and_type(src);
|
||||
return cast_const_raw_ptr( // Originally type_caster_generic::cast.
|
||||
st.first,
|
||||
policy,
|
||||
parent,
|
||||
st.second,
|
||||
make_constructor::make_copy_constructor(src),
|
||||
make_constructor::make_move_constructor(src));
|
||||
}
|
||||
|
||||
static handle cast(T *src, return_value_policy policy, handle parent) {
|
||||
return cast(const_cast<T const *>(src), policy, parent); // Mutbl2Const
|
||||
}
|
||||
|
||||
#if defined(_MSC_VER) && _MSC_VER < 1910
|
||||
// Working around MSVC 2015 bug. const-correctness is lost.
|
||||
// SMART_HOLDER_WIP: IMPROVABLE: make common code work with MSVC 2015.
|
||||
template <typename T_>
|
||||
using cast_op_type = detail::cast_op_type<T_>;
|
||||
#else
|
||||
template <typename T_>
|
||||
using cast_op_type = conditional_t<
|
||||
std::is_same<remove_reference_t<T_>, T const *>::value,
|
||||
T const *,
|
||||
conditional_t<std::is_same<remove_reference_t<T_>, T *>::value,
|
||||
T *,
|
||||
conditional_t<std::is_same<T_, T const &>::value, T const &, T &>>>;
|
||||
#endif
|
||||
|
||||
// The const operators here prove that the existing type_caster mechanism already supports
|
||||
// const-correctness. However, fully implementing const-correctness inside this type_caster
|
||||
// is still a major project.
|
||||
operator T const &() const {
|
||||
return const_cast<smart_holder_type_caster *>(this)->loaded_as_lvalue_ref();
|
||||
}
|
||||
operator T const *() const {
|
||||
return const_cast<smart_holder_type_caster *>(this)->loaded_as_raw_ptr_unowned();
|
||||
}
|
||||
operator T &() { return this->loaded_as_lvalue_ref(); }
|
||||
operator T *() { return this->loaded_as_raw_ptr_unowned(); }
|
||||
|
||||
// Originally type_caster_generic::cast.
|
||||
PYBIND11_NOINLINE static handle cast_const_raw_ptr(const void *_src,
|
||||
return_value_policy policy,
|
||||
handle parent,
|
||||
const detail::type_info *tinfo,
|
||||
void *(*copy_constructor)(const void *),
|
||||
void *(*move_constructor)(const void *),
|
||||
const void *existing_holder = nullptr) {
|
||||
if (!tinfo) // no type info: error will be set already
|
||||
return handle();
|
||||
|
||||
void *src = const_cast<void *>(_src);
|
||||
if (src == nullptr)
|
||||
return none().release();
|
||||
|
||||
if (handle existing_inst = find_registered_python_instance(src, tinfo))
|
||||
return existing_inst;
|
||||
|
||||
auto inst = reinterpret_steal<object>(make_new_instance(tinfo->type));
|
||||
auto wrapper = reinterpret_cast<instance *>(inst.ptr());
|
||||
wrapper->owned = false;
|
||||
void *&valueptr = values_and_holders(wrapper).begin()->value_ptr();
|
||||
|
||||
switch (policy) {
|
||||
case return_value_policy::automatic:
|
||||
case return_value_policy::take_ownership:
|
||||
valueptr = src;
|
||||
wrapper->owned = true;
|
||||
break;
|
||||
|
||||
case return_value_policy::automatic_reference:
|
||||
case return_value_policy::reference:
|
||||
valueptr = src;
|
||||
wrapper->owned = false;
|
||||
break;
|
||||
|
||||
case return_value_policy::copy:
|
||||
if (copy_constructor)
|
||||
valueptr = copy_constructor(src);
|
||||
else {
|
||||
#if defined(NDEBUG)
|
||||
throw cast_error("return_value_policy = copy, but type is "
|
||||
"non-copyable! (compile in debug mode for details)");
|
||||
#else
|
||||
std::string type_name(tinfo->cpptype->name());
|
||||
detail::clean_type_id(type_name);
|
||||
throw cast_error("return_value_policy = copy, but type " + type_name
|
||||
+ " is non-copyable!");
|
||||
#endif
|
||||
}
|
||||
wrapper->owned = true;
|
||||
break;
|
||||
|
||||
case return_value_policy::move:
|
||||
if (move_constructor)
|
||||
valueptr = move_constructor(src);
|
||||
else if (copy_constructor)
|
||||
valueptr = copy_constructor(src);
|
||||
else {
|
||||
#if defined(NDEBUG)
|
||||
throw cast_error("return_value_policy = move, but type is neither "
|
||||
"movable nor copyable! "
|
||||
"(compile in debug mode for details)");
|
||||
#else
|
||||
std::string type_name(tinfo->cpptype->name());
|
||||
detail::clean_type_id(type_name);
|
||||
throw cast_error("return_value_policy = move, but type " + type_name
|
||||
+ " is neither movable nor copyable!");
|
||||
#endif
|
||||
}
|
||||
wrapper->owned = true;
|
||||
break;
|
||||
|
||||
case return_value_policy::reference_internal:
|
||||
valueptr = src;
|
||||
wrapper->owned = false;
|
||||
keep_alive_impl(inst, parent);
|
||||
break;
|
||||
|
||||
default:
|
||||
throw cast_error("unhandled return_value_policy: should not happen!");
|
||||
}
|
||||
|
||||
tinfo->init_instance(wrapper, existing_holder);
|
||||
|
||||
return inst.release();
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
struct smart_holder_type_caster<std::shared_ptr<T>> : smart_holder_type_caster_load<T>,
|
||||
smart_holder_type_caster_class_hooks {
|
||||
static constexpr auto name = _<std::shared_ptr<T>>();
|
||||
|
||||
static handle cast(const std::shared_ptr<T> &src, return_value_policy policy, handle parent) {
|
||||
if (policy != return_value_policy::automatic
|
||||
&& policy != return_value_policy::reference_internal) {
|
||||
// SMART_HOLDER_WIP: IMPROVABLE: Error message.
|
||||
throw cast_error("Invalid return_value_policy for shared_ptr.");
|
||||
}
|
||||
|
||||
auto src_raw_ptr = src.get();
|
||||
auto st = type_caster_base<T>::src_and_type(src_raw_ptr);
|
||||
if (st.first == nullptr)
|
||||
return none().release(); // PyErr was set already.
|
||||
|
||||
void *src_raw_void_ptr = static_cast<void *>(src_raw_ptr);
|
||||
const detail::type_info *tinfo = st.second;
|
||||
if (handle existing_inst = find_registered_python_instance(src_raw_void_ptr, tinfo))
|
||||
// SMART_HOLDER_WIP: MISSING: Enforcement of consistency with existing smart_holder.
|
||||
// SMART_HOLDER_WIP: MISSING: keep_alive.
|
||||
return existing_inst;
|
||||
|
||||
auto inst = reinterpret_steal<object>(make_new_instance(tinfo->type));
|
||||
auto *inst_raw_ptr = reinterpret_cast<instance *>(inst.ptr());
|
||||
inst_raw_ptr->owned = true;
|
||||
void *&valueptr = values_and_holders(inst_raw_ptr).begin()->value_ptr();
|
||||
valueptr = src_raw_void_ptr;
|
||||
|
||||
auto smhldr = pybindit::memory::smart_holder::from_shared_ptr(src);
|
||||
tinfo->init_instance(inst_raw_ptr, static_cast<const void *>(&smhldr));
|
||||
|
||||
if (policy == return_value_policy::reference_internal)
|
||||
keep_alive_impl(inst, parent);
|
||||
|
||||
return inst.release();
|
||||
}
|
||||
|
||||
template <typename>
|
||||
using cast_op_type = std::shared_ptr<T>;
|
||||
|
||||
operator std::shared_ptr<T>() { return this->loaded_as_shared_ptr(); }
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
struct smart_holder_type_caster<std::shared_ptr<T const>> : smart_holder_type_caster_load<T>,
|
||||
smart_holder_type_caster_class_hooks {
|
||||
static constexpr auto name = _<std::shared_ptr<T const>>();
|
||||
|
||||
static handle
|
||||
cast(const std::shared_ptr<T const> &src, return_value_policy policy, handle parent) {
|
||||
return smart_holder_type_caster<std::shared_ptr<T>>::cast(
|
||||
std::const_pointer_cast<T>(src), // Const2Mutbl
|
||||
policy,
|
||||
parent);
|
||||
}
|
||||
|
||||
template <typename>
|
||||
using cast_op_type = std::shared_ptr<T const>;
|
||||
|
||||
operator std::shared_ptr<T const>() { return this->loaded_as_shared_ptr(); } // Mutbl2Const
|
||||
};
|
||||
|
||||
template <typename T, typename D>
|
||||
struct smart_holder_type_caster<std::unique_ptr<T, D>> : smart_holder_type_caster_load<T>,
|
||||
smart_holder_type_caster_class_hooks {
|
||||
static constexpr auto name = _<std::unique_ptr<T, D>>();
|
||||
|
||||
static handle cast(std::unique_ptr<T, D> &&src, return_value_policy policy, handle parent) {
|
||||
if (policy != return_value_policy::automatic
|
||||
&& policy != return_value_policy::reference_internal
|
||||
&& policy != return_value_policy::move) {
|
||||
// SMART_HOLDER_WIP: IMPROVABLE: Error message.
|
||||
throw cast_error("Invalid return_value_policy for unique_ptr.");
|
||||
}
|
||||
|
||||
auto src_raw_ptr = src.get();
|
||||
auto st = type_caster_base<T>::src_and_type(src_raw_ptr);
|
||||
if (st.first == nullptr)
|
||||
return none().release(); // PyErr was set already.
|
||||
|
||||
void *src_raw_void_ptr = static_cast<void *>(src_raw_ptr);
|
||||
const detail::type_info *tinfo = st.second;
|
||||
if (find_registered_python_instance(src_raw_void_ptr, tinfo))
|
||||
throw cast_error("Invalid unique_ptr: another instance owns this pointer already.");
|
||||
|
||||
auto inst = reinterpret_steal<object>(make_new_instance(tinfo->type));
|
||||
auto *inst_raw_ptr = reinterpret_cast<instance *>(inst.ptr());
|
||||
inst_raw_ptr->owned = true;
|
||||
void *&valueptr = values_and_holders(inst_raw_ptr).begin()->value_ptr();
|
||||
valueptr = src_raw_void_ptr;
|
||||
|
||||
auto smhldr = pybindit::memory::smart_holder::from_unique_ptr(std::move(src));
|
||||
tinfo->init_instance(inst_raw_ptr, static_cast<const void *>(&smhldr));
|
||||
|
||||
if (policy == return_value_policy::reference_internal)
|
||||
keep_alive_impl(inst, parent);
|
||||
|
||||
return inst.release();
|
||||
}
|
||||
|
||||
template <typename>
|
||||
using cast_op_type = std::unique_ptr<T, D>;
|
||||
|
||||
operator std::unique_ptr<T, D>() { return this->template loaded_as_unique_ptr<D>(); }
|
||||
};
|
||||
|
||||
template <typename T, typename D>
|
||||
struct smart_holder_type_caster<std::unique_ptr<T const, D>>
|
||||
: smart_holder_type_caster_load<T>, smart_holder_type_caster_class_hooks {
|
||||
static constexpr auto name = _<std::unique_ptr<T const, D>>();
|
||||
|
||||
static handle
|
||||
cast(std::unique_ptr<T const, D> &&src, return_value_policy policy, handle parent) {
|
||||
return smart_holder_type_caster<std::unique_ptr<T, D>>::cast(
|
||||
std::unique_ptr<T, D>(const_cast<T *>(src.release())), // Const2Mutbl
|
||||
policy,
|
||||
parent);
|
||||
}
|
||||
|
||||
template <typename>
|
||||
using cast_op_type = std::unique_ptr<T const, D>;
|
||||
|
||||
operator std::unique_ptr<T const, D>() { return this->template loaded_as_unique_ptr<D>(); }
|
||||
};
|
||||
|
||||
#ifndef PYBIND11_USE_SMART_HOLDER_AS_DEFAULT
|
||||
|
||||
# define PYBIND11_SMART_HOLDER_TYPE_CASTERS(T) \
|
||||
namespace pybind11 { \
|
||||
namespace detail { \
|
||||
template <> \
|
||||
class type_caster<T> : public smart_holder_type_caster<T> {}; \
|
||||
template <> \
|
||||
class type_caster<std::shared_ptr<T>> \
|
||||
: public smart_holder_type_caster<std::shared_ptr<T>> {}; \
|
||||
template <> \
|
||||
class type_caster<std::shared_ptr<T const>> \
|
||||
: public smart_holder_type_caster<std::shared_ptr<T const>> {}; \
|
||||
template <typename D> \
|
||||
class type_caster<std::unique_ptr<T, D>> \
|
||||
: public smart_holder_type_caster<std::unique_ptr<T, D>> {}; \
|
||||
template <typename D> \
|
||||
class type_caster<std::unique_ptr<T const, D>> \
|
||||
: public smart_holder_type_caster<std::unique_ptr<T const, D>> {}; \
|
||||
} \
|
||||
}
|
||||
#else
|
||||
|
||||
# define PYBIND11_SMART_HOLDER_TYPE_CASTERS(T)
|
||||
|
||||
template <typename T>
|
||||
class type_caster_for_class_ : public smart_holder_type_caster<T> {};
|
||||
|
||||
template <typename T>
|
||||
class type_caster_for_class_<std::shared_ptr<T>>
|
||||
: public smart_holder_type_caster<std::shared_ptr<T>> {};
|
||||
|
||||
template <typename T>
|
||||
class type_caster_for_class_<std::shared_ptr<T const>>
|
||||
: public smart_holder_type_caster<std::shared_ptr<T const>> {};
|
||||
|
||||
template <typename T, typename D>
|
||||
class type_caster_for_class_<std::unique_ptr<T, D>>
|
||||
: public smart_holder_type_caster<std::unique_ptr<T, D>> {};
|
||||
|
||||
template <typename T, typename D>
|
||||
class type_caster_for_class_<std::unique_ptr<T const, D>>
|
||||
: public smart_holder_type_caster<std::unique_ptr<T const, D>> {};
|
||||
|
||||
#endif
|
||||
|
||||
PYBIND11_NAMESPACE_END(detail)
|
||||
PYBIND11_NAMESPACE_END(PYBIND11_NAMESPACE)
|
@ -1,3 +1,4 @@
|
||||
// clang-format off
|
||||
/*
|
||||
pybind11/pybind11.h: Main header file of the C++11 python
|
||||
binding generator library
|
||||
@ -10,43 +11,12 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#if defined(__INTEL_COMPILER)
|
||||
# pragma warning push
|
||||
# pragma warning disable 68 // integer conversion resulted in a change of sign
|
||||
# pragma warning disable 186 // pointless comparison of unsigned integer with zero
|
||||
# pragma warning disable 878 // incompatible exception specifications
|
||||
# pragma warning disable 1334 // the "template" keyword used for syntactic disambiguation may only be used within a template
|
||||
# pragma warning disable 1682 // implicit conversion of a 64-bit integral type to a smaller integral type (potential portability problem)
|
||||
# pragma warning disable 1786 // function "strdup" was declared deprecated
|
||||
# pragma warning disable 1875 // offsetof applied to non-POD (Plain Old Data) types is nonstandard
|
||||
# pragma warning disable 2196 // warning #2196: routine is both "inline" and "noinline"
|
||||
#elif defined(_MSC_VER)
|
||||
# pragma warning(push)
|
||||
# pragma warning(disable: 4100) // warning C4100: Unreferenced formal parameter
|
||||
# pragma warning(disable: 4127) // warning C4127: Conditional expression is constant
|
||||
# pragma warning(disable: 4512) // warning C4512: Assignment operator was implicitly defined as deleted
|
||||
# pragma warning(disable: 4800) // warning C4800: 'int': forcing value to bool 'true' or 'false' (performance warning)
|
||||
# pragma warning(disable: 4996) // warning C4996: The POSIX name for this item is deprecated. Instead, use the ISO C and C++ conformant name
|
||||
# pragma warning(disable: 4702) // warning C4702: unreachable code
|
||||
# pragma warning(disable: 4522) // warning C4522: multiple assignment operators specified
|
||||
# pragma warning(disable: 4505) // warning C4505: 'PySlice_GetIndicesEx': unreferenced local function has been removed (PyPy only)
|
||||
#elif defined(__GNUG__) && !defined(__clang__)
|
||||
# pragma GCC diagnostic push
|
||||
# pragma GCC diagnostic ignored "-Wunused-but-set-parameter"
|
||||
# pragma GCC diagnostic ignored "-Wunused-but-set-variable"
|
||||
# pragma GCC diagnostic ignored "-Wmissing-field-initializers"
|
||||
# pragma GCC diagnostic ignored "-Wstrict-aliasing"
|
||||
# pragma GCC diagnostic ignored "-Wattributes"
|
||||
# if __GNUC__ >= 7
|
||||
# pragma GCC diagnostic ignored "-Wnoexcept-type"
|
||||
# endif
|
||||
#endif
|
||||
|
||||
#include "attr.h"
|
||||
#include "gil.h"
|
||||
#include "options.h"
|
||||
#include "detail/class.h"
|
||||
#include "detail/init.h"
|
||||
#include "detail/smart_holder_sfinae_hooks_only.h"
|
||||
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
@ -1081,7 +1051,8 @@ class generic_type : public object {
|
||||
public:
|
||||
PYBIND11_OBJECT_DEFAULT(generic_type, object, PyType_Check)
|
||||
protected:
|
||||
void initialize(const type_record &rec) {
|
||||
void initialize(const type_record &rec,
|
||||
void *(*type_caster_module_local_load)(PyObject *, const type_info *)) {
|
||||
if (rec.scope && hasattr(rec.scope, "__dict__") && rec.scope.attr("__dict__").contains(rec.name))
|
||||
pybind11_fail("generic_type: cannot initialize type \"" + std::string(rec.name) +
|
||||
"\": an object with that name is already defined");
|
||||
@ -1127,7 +1098,7 @@ protected:
|
||||
|
||||
if (rec.module_local) {
|
||||
// Stash the local typeinfo and loader so that external modules can access it.
|
||||
tinfo->module_local_load = &type_caster_generic::local_load;
|
||||
tinfo->module_local_load = type_caster_module_local_load;
|
||||
setattr(m_ptr, PYBIND11_MODULE_LOCAL_ID, capsule(tinfo));
|
||||
}
|
||||
}
|
||||
@ -1240,11 +1211,45 @@ auto method_adaptor(Return (Class::*pmf)(Args...) const) -> Return (Derived::*)(
|
||||
return pmf;
|
||||
}
|
||||
|
||||
// clang-format on
|
||||
template <typename T>
|
||||
#ifndef PYBIND11_USE_SMART_HOLDER_AS_DEFAULT
|
||||
|
||||
using default_holder_type = std::unique_ptr<T>;
|
||||
|
||||
# define PYBIND11_SMART_POINTER_HOLDER_TYPE_CASTERS(T, ...)
|
||||
|
||||
#else
|
||||
|
||||
using default_holder_type = smart_holder;
|
||||
|
||||
// This define could be hidden away inside detail/smart_holder_type_casters.h, but is kept here
|
||||
// for clarity.
|
||||
# define PYBIND11_SMART_POINTER_HOLDER_TYPE_CASTERS(T, ...) \
|
||||
namespace pybind11 { \
|
||||
namespace detail { \
|
||||
template <> \
|
||||
class type_caster<T> : public type_caster_base<T> {}; \
|
||||
template <> \
|
||||
class type_caster<__VA_ARGS__> : public type_caster_holder<T, __VA_ARGS__> {}; \
|
||||
} \
|
||||
}
|
||||
|
||||
#endif
|
||||
// clang-format off
|
||||
|
||||
template <typename type_, typename... options>
|
||||
class class_ : public detail::generic_type {
|
||||
template <typename T> using is_holder = detail::is_holder_type<type_, T>;
|
||||
template <typename T> using is_subtype = detail::is_strict_base_of<type_, T>;
|
||||
template <typename T> using is_base = detail::is_strict_base_of<T, type_>;
|
||||
template <typename T>
|
||||
// clang-format on
|
||||
using is_holder
|
||||
= detail::any_of<detail::is_holder_type<type_, T>,
|
||||
detail::all_of<detail::negation<is_base<T>>,
|
||||
detail::negation<is_subtype<T>>,
|
||||
detail::type_uses_smart_holder_type_caster<type_>>>;
|
||||
// clang-format off
|
||||
// struct instead of using here to help MSVC:
|
||||
template <typename T> struct is_valid_class_option :
|
||||
detail::any_of<is_holder<T>, is_subtype<T>, is_base<T>> {};
|
||||
@ -1253,7 +1258,7 @@ public:
|
||||
using type = type_;
|
||||
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 holder_type = detail::exactly_one_t<is_holder, default_holder_type<type>, options...>;
|
||||
|
||||
static_assert(detail::all_of<is_valid_class_option<options>...>::value,
|
||||
"Unknown/invalid class_ template parameters provided");
|
||||
@ -1275,6 +1280,39 @@ public:
|
||||
none_of<std::is_same<multiple_inheritance, Extra>...>::value), // no multiple_inheritance attr
|
||||
"Error: multiple inheritance bases must be specified via class_ template options");
|
||||
|
||||
// clang-format on
|
||||
static constexpr bool holder_is_smart_holder
|
||||
= detail::is_smart_holder_type<holder_type>::value;
|
||||
static constexpr bool wrapped_type_uses_smart_holder_type_caster
|
||||
= detail::type_uses_smart_holder_type_caster<type>::value;
|
||||
static constexpr bool type_caster_type_is_type_caster_base_subtype
|
||||
= std::is_base_of<detail::type_caster_base<type>, detail::type_caster<type>>::value;
|
||||
// Necessary conditions, but not strict.
|
||||
static_assert(
|
||||
!(detail::is_instantiation<std::unique_ptr, holder_type>::value
|
||||
&& wrapped_type_uses_smart_holder_type_caster),
|
||||
"py::class_ holder vs type_caster mismatch:"
|
||||
" missing PYBIND11_SMART_POINTER_HOLDER_TYPE_CASTERS(T, std::unique_ptr<T>)?");
|
||||
static_assert(
|
||||
!(detail::is_instantiation<std::shared_ptr, holder_type>::value
|
||||
&& wrapped_type_uses_smart_holder_type_caster),
|
||||
"py::class_ holder vs type_caster mismatch:"
|
||||
" missing PYBIND11_SMART_POINTER_HOLDER_TYPE_CASTERS(T, std::shared_ptr<T>)?");
|
||||
static_assert(!(holder_is_smart_holder && type_caster_type_is_type_caster_base_subtype),
|
||||
"py::class_ holder vs type_caster mismatch:"
|
||||
" missing PYBIND11_SMART_HOLDER_TYPE_CASTERS(T)?");
|
||||
#ifdef PYBIND11_STRICT_ASSERTS_CLASS_HOLDER_VS_TYPE_CASTER_MIX
|
||||
// Strict conditions cannot be enforced universally at the moment (PR #2836).
|
||||
static_assert(holder_is_smart_holder == wrapped_type_uses_smart_holder_type_caster,
|
||||
"py::class_ holder vs type_caster mismatch:"
|
||||
" missing PYBIND11_SMART_HOLDER_TYPE_CASTERS(T)"
|
||||
" or collision with custom py::detail::type_caster<T>?");
|
||||
static_assert(!holder_is_smart_holder == type_caster_type_is_type_caster_base_subtype,
|
||||
"py::class_ holder vs type_caster mismatch:"
|
||||
" missing PYBIND11_SMART_POINTER_HOLDER_TYPE_CASTERS(T, ...)"
|
||||
" or collision with custom py::detail::type_caster<T>?");
|
||||
#endif
|
||||
// clang-format off
|
||||
type_record record;
|
||||
record.scope = scope;
|
||||
record.name = name;
|
||||
@ -1284,6 +1322,8 @@ public:
|
||||
record.holder_size = sizeof(holder_type);
|
||||
record.init_instance = init_instance;
|
||||
record.dealloc = dealloc;
|
||||
|
||||
// A more fitting name would be uses_unique_ptr_holder.
|
||||
record.default_holder = detail::is_instantiation<std::unique_ptr, holder_type>::value;
|
||||
|
||||
set_operator_new<type>(&record);
|
||||
@ -1294,7 +1334,7 @@ public:
|
||||
/* Process optional arguments, if any */
|
||||
process_attributes<Extra...>::init(extra..., &record);
|
||||
|
||||
generic_type::initialize(record);
|
||||
generic_type_initialize(record);
|
||||
|
||||
if (has_alias) {
|
||||
auto &instances = record.module_local ? registered_local_types_cpp() : get_internals().registered_types_cpp;
|
||||
@ -1502,6 +1542,20 @@ public:
|
||||
}
|
||||
|
||||
private:
|
||||
// clang-format on
|
||||
template <typename T = type,
|
||||
detail::enable_if_t<!detail::type_uses_smart_holder_type_caster<T>::value, int> = 0>
|
||||
void generic_type_initialize(const detail::type_record &record) {
|
||||
generic_type::initialize(record, &detail::type_caster_generic::local_load);
|
||||
}
|
||||
|
||||
template <typename T = type,
|
||||
detail::enable_if_t<detail::type_uses_smart_holder_type_caster<T>::value, int> = 0>
|
||||
void generic_type_initialize(const detail::type_record &record) {
|
||||
generic_type::initialize(record, detail::type_caster<T>::get_local_load_function_ptr());
|
||||
}
|
||||
// clang-format off
|
||||
|
||||
/// Initialize holder object, variant 1: object derives from enable_shared_from_this
|
||||
template <typename T>
|
||||
static void init_holder(detail::instance *inst, detail::value_and_holder &v_h,
|
||||
@ -1546,6 +1600,9 @@ private:
|
||||
/// instance. Should be called as soon as the `type` value_ptr is set for an instance. Takes an
|
||||
/// optional pointer to an existing holder to use; if not specified and the instance is
|
||||
/// `.owned`, a new holder will be constructed to manage the value pointer.
|
||||
template <
|
||||
typename T = type,
|
||||
detail::enable_if_t<!detail::type_uses_smart_holder_type_caster<T>::value, int> = 0>
|
||||
static void init_instance(detail::instance *inst, const void *holder_ptr) {
|
||||
auto v_h = inst->get_value_and_holder(detail::get_type_info(typeid(type)));
|
||||
if (!v_h.instance_registered()) {
|
||||
@ -1555,6 +1612,14 @@ private:
|
||||
init_holder(inst, v_h, (const holder_type *) holder_ptr, v_h.value_ptr<type>());
|
||||
}
|
||||
|
||||
// clang-format on
|
||||
template <typename T = type,
|
||||
detail::enable_if_t<detail::type_uses_smart_holder_type_caster<T>::value, int> = 0>
|
||||
static void init_instance(detail::instance *inst, const void *holder_ptr) {
|
||||
detail::type_caster<T>::template init_instance_for_type<type>(inst, holder_ptr);
|
||||
}
|
||||
// clang-format off
|
||||
|
||||
/// Deallocates an instance; via holder, if constructed; otherwise via operator delete.
|
||||
static void dealloc(detail::value_and_holder &v_h) {
|
||||
// We could be deallocating because we are cleaning up after a Python exception.
|
||||
|
23
include/pybind11/smart_holder.h
Normal file
23
include/pybind11/smart_holder.h
Normal file
@ -0,0 +1,23 @@
|
||||
// Copyright (c) 2021 The Pybind Development Team.
|
||||
// All rights reserved. Use of this source code is governed by a
|
||||
// BSD-style license that can be found in the LICENSE file.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "detail/common.h"
|
||||
#include "detail/smart_holder_type_casters.h"
|
||||
#include "pybind11.h"
|
||||
|
||||
PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE)
|
||||
|
||||
// Supports easier switching between py::class_<U> and py::class_<U, py::smart_holder>:
|
||||
// users can simply replace the `_` in `class_` with `h` or vice versa.
|
||||
// Note though that the PYBIND11_SMART_HOLDER_TYPE_CASTERS(U) macro also needs to be
|
||||
// added (for `classh`) or commented out (for `class_`).
|
||||
template <typename type_, typename... options>
|
||||
class classh : public class_<type_, smart_holder, options...> {
|
||||
public:
|
||||
using class_<type_, smart_holder, options...>::class_;
|
||||
};
|
||||
|
||||
PYBIND11_NAMESPACE_END(PYBIND11_NAMESPACE)
|
@ -438,7 +438,7 @@ PYBIND11_NAMESPACE_END(detail)
|
||||
//
|
||||
// std::vector
|
||||
//
|
||||
template <typename Vector, typename holder_type = std::unique_ptr<Vector>, typename... Args>
|
||||
template <typename Vector, typename holder_type = default_holder_type<Vector>, typename... Args>
|
||||
class_<Vector, holder_type> bind_vector(handle scope, std::string const &name, Args&&... args) {
|
||||
using Class_ = class_<Vector, holder_type>;
|
||||
|
||||
@ -599,7 +599,7 @@ template <typename Map, typename Class_> auto map_if_insertion_operator(Class_ &
|
||||
|
||||
PYBIND11_NAMESPACE_END(detail)
|
||||
|
||||
template <typename Map, typename holder_type = std::unique_ptr<Map>, typename... Args>
|
||||
template <typename Map, typename holder_type = default_holder_type<Map>, typename... Args>
|
||||
class_<Map, holder_type> bind_map(handle scope, const std::string &name, Args&&... args) {
|
||||
using KeyType = typename Map::key_type;
|
||||
using MappedType = typename Map::mapped_type;
|
||||
|
@ -101,6 +101,10 @@ set(PYBIND11_TEST_FILES
|
||||
test_callbacks.cpp
|
||||
test_chrono.cpp
|
||||
test_class.cpp
|
||||
test_class_sh_basic.cpp
|
||||
test_class_sh_factory_constructors.cpp
|
||||
test_class_sh_inheritance.cpp
|
||||
test_class_sh_unique_ptr_member.cpp
|
||||
test_constants_and_functions.cpp
|
||||
test_copy_move.cpp
|
||||
test_custom_type_casters.cpp
|
||||
@ -160,6 +164,8 @@ if(PYBIND11_CUDA_TESTS)
|
||||
endif()
|
||||
|
||||
string(REPLACE ".cpp" ".py" PYBIND11_PYTEST_FILES "${PYBIND11_TEST_FILES}")
|
||||
list(APPEND PYBIND11_PYTEST_FILES test_class_sh_module_local.py)
|
||||
list(SORT PYBIND11_PYTEST_FILES)
|
||||
|
||||
# Contains the set of test files that require pybind11_cross_module_tests to be
|
||||
# built; if none of these are built (i.e. because TEST_OVERRIDE is used and
|
||||
@ -169,6 +175,8 @@ set(PYBIND11_CROSS_MODULE_TESTS test_exceptions.py test_local_bindings.py test_s
|
||||
|
||||
set(PYBIND11_CROSS_MODULE_GIL_TESTS test_gil_scoped.py)
|
||||
|
||||
set(PYBIND11_CLASS_SH_MODULE_LOCAL_TESTS test_class_sh_module_local.py)
|
||||
|
||||
# Check if Eigen is available; if not, remove from PYBIND11_TEST_FILES (but
|
||||
# keep it in PYBIND11_PYTEST_FILES, so that we get the "eigen is not installed"
|
||||
# skip message).
|
||||
@ -304,6 +312,16 @@ foreach(t ${PYBIND11_CROSS_MODULE_GIL_TESTS})
|
||||
endif()
|
||||
endforeach()
|
||||
|
||||
foreach(t ${PYBIND11_CLASS_SH_MODULE_LOCAL_TESTS})
|
||||
list(FIND PYBIND11_PYTEST_FILES ${t} i)
|
||||
if(i GREATER -1)
|
||||
list(APPEND test_targets class_sh_module_local_0)
|
||||
list(APPEND test_targets class_sh_module_local_1)
|
||||
list(APPEND test_targets class_sh_module_local_2)
|
||||
break()
|
||||
endif()
|
||||
endforeach()
|
||||
|
||||
# Support CUDA testing by forcing the target file to compile with NVCC
|
||||
if(PYBIND11_CUDA_TESTS)
|
||||
set_property(SOURCE ${PYBIND11_TEST_FILES} PROPERTY LANGUAGE CUDA)
|
||||
@ -443,6 +461,9 @@ add_custom_command(
|
||||
${CMAKE_CURRENT_BINARY_DIR}/sosize-$<TARGET_FILE_NAME:pybind11_tests>.txt)
|
||||
|
||||
if(NOT PYBIND11_CUDA_TESTS)
|
||||
# Test pure C++ code (not depending on Python). Provides the `test_pure_cpp` target.
|
||||
add_subdirectory(pure_cpp)
|
||||
|
||||
# Test embedding the interpreter. Provides the `cpptest` target.
|
||||
add_subdirectory(test_embed)
|
||||
|
||||
|
27
tests/class_sh_module_local_0.cpp
Normal file
27
tests/class_sh_module_local_0.cpp
Normal file
@ -0,0 +1,27 @@
|
||||
#include <pybind11/smart_holder.h>
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace pybind11_tests {
|
||||
namespace class_sh_module_local {
|
||||
|
||||
struct atyp { // Short for "any type".
|
||||
std::string mtxt;
|
||||
};
|
||||
|
||||
std::string get_mtxt(const atyp &obj) { return obj.mtxt; }
|
||||
|
||||
atyp rtrn_valu_atyp() { return atyp(); }
|
||||
|
||||
} // namespace class_sh_module_local
|
||||
} // namespace pybind11_tests
|
||||
|
||||
PYBIND11_SMART_HOLDER_TYPE_CASTERS(pybind11_tests::class_sh_module_local::atyp)
|
||||
|
||||
PYBIND11_MODULE(class_sh_module_local_0, m) {
|
||||
using namespace pybind11_tests::class_sh_module_local;
|
||||
|
||||
m.def("get_mtxt", get_mtxt);
|
||||
|
||||
m.def("rtrn_valu_atyp", rtrn_valu_atyp);
|
||||
}
|
33
tests/class_sh_module_local_1.cpp
Normal file
33
tests/class_sh_module_local_1.cpp
Normal file
@ -0,0 +1,33 @@
|
||||
// Identical to class_sh_module_local_2.cpp, except 2 replaced with 1.
|
||||
#include <pybind11/smart_holder.h>
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace pybind11_tests {
|
||||
namespace class_sh_module_local {
|
||||
|
||||
struct atyp { // Short for "any type".
|
||||
std::string mtxt;
|
||||
};
|
||||
|
||||
std::string get_mtxt(const atyp &obj) { return obj.mtxt; }
|
||||
|
||||
} // namespace class_sh_module_local
|
||||
} // namespace pybind11_tests
|
||||
|
||||
PYBIND11_SMART_HOLDER_TYPE_CASTERS(pybind11_tests::class_sh_module_local::atyp)
|
||||
|
||||
PYBIND11_MODULE(class_sh_module_local_1, m) {
|
||||
namespace py = pybind11;
|
||||
using namespace pybind11_tests::class_sh_module_local;
|
||||
|
||||
py::classh<atyp>(m, "atyp", py::module_local())
|
||||
.def(py::init([](const std::string &mtxt) {
|
||||
atyp obj;
|
||||
obj.mtxt = mtxt;
|
||||
return obj;
|
||||
}))
|
||||
.def("tag", [](const atyp &) { return 1; });
|
||||
|
||||
m.def("get_mtxt", get_mtxt);
|
||||
}
|
33
tests/class_sh_module_local_2.cpp
Normal file
33
tests/class_sh_module_local_2.cpp
Normal file
@ -0,0 +1,33 @@
|
||||
// Identical to class_sh_module_local_1.cpp, except 1 replaced with 2.
|
||||
#include <pybind11/smart_holder.h>
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace pybind11_tests {
|
||||
namespace class_sh_module_local {
|
||||
|
||||
struct atyp { // Short for "any type".
|
||||
std::string mtxt;
|
||||
};
|
||||
|
||||
std::string get_mtxt(const atyp &obj) { return obj.mtxt; }
|
||||
|
||||
} // namespace class_sh_module_local
|
||||
} // namespace pybind11_tests
|
||||
|
||||
PYBIND11_SMART_HOLDER_TYPE_CASTERS(pybind11_tests::class_sh_module_local::atyp)
|
||||
|
||||
PYBIND11_MODULE(class_sh_module_local_2, m) {
|
||||
namespace py = pybind11;
|
||||
using namespace pybind11_tests::class_sh_module_local;
|
||||
|
||||
py::classh<atyp>(m, "atyp", py::module_local())
|
||||
.def(py::init([](const std::string &mtxt) {
|
||||
atyp obj;
|
||||
obj.mtxt = mtxt;
|
||||
return obj;
|
||||
}))
|
||||
.def("tag", [](const atyp &) { return 2; });
|
||||
|
||||
m.def("get_mtxt", get_mtxt);
|
||||
}
|
@ -32,6 +32,7 @@ main_headers = {
|
||||
"include/pybind11/options.h",
|
||||
"include/pybind11/pybind11.h",
|
||||
"include/pybind11/pytypes.h",
|
||||
"include/pybind11/smart_holder.h",
|
||||
"include/pybind11/stl.h",
|
||||
"include/pybind11/stl_bind.h",
|
||||
}
|
||||
@ -42,6 +43,9 @@ detail_headers = {
|
||||
"include/pybind11/detail/descr.h",
|
||||
"include/pybind11/detail/init.h",
|
||||
"include/pybind11/detail/internals.h",
|
||||
"include/pybind11/detail/smart_holder_poc.h",
|
||||
"include/pybind11/detail/smart_holder_sfinae_hooks_only.h",
|
||||
"include/pybind11/detail/smart_holder_type_casters.h",
|
||||
"include/pybind11/detail/type_caster_base.h",
|
||||
"include/pybind11/detail/typeid.h",
|
||||
}
|
||||
|
20
tests/pure_cpp/CMakeLists.txt
Normal file
20
tests/pure_cpp/CMakeLists.txt
Normal file
@ -0,0 +1,20 @@
|
||||
find_package(Catch 2.13.2)
|
||||
|
||||
if(CATCH_FOUND)
|
||||
message(STATUS "Building pure C++ tests (not depending on Python) using Catch v${CATCH_VERSION}")
|
||||
else()
|
||||
message(STATUS "Catch not detected. Interpreter tests will be skipped. Install Catch headers"
|
||||
" manually or use `cmake -DDOWNLOAD_CATCH=ON` to fetch them automatically.")
|
||||
return()
|
||||
endif()
|
||||
|
||||
add_executable(smart_holder_poc_test smart_holder_poc_test.cpp)
|
||||
pybind11_enable_warnings(smart_holder_poc_test)
|
||||
target_link_libraries(smart_holder_poc_test PRIVATE pybind11::headers Catch2::Catch2)
|
||||
|
||||
add_custom_target(
|
||||
test_pure_cpp
|
||||
COMMAND "$<TARGET_FILE:smart_holder_poc_test>"
|
||||
WORKING_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}")
|
||||
|
||||
add_dependencies(check test_pure_cpp)
|
281
tests/pure_cpp/smart_holder_poc_test.cpp
Normal file
281
tests/pure_cpp/smart_holder_poc_test.cpp
Normal file
@ -0,0 +1,281 @@
|
||||
#include "pybind11/detail/smart_holder_poc.h"
|
||||
|
||||
#define CATCH_CONFIG_MAIN
|
||||
#include "catch.hpp"
|
||||
|
||||
using pybindit::memory::smart_holder;
|
||||
|
||||
namespace helpers {
|
||||
|
||||
struct movable_int {
|
||||
int valu;
|
||||
movable_int(int v) : valu{v} {}
|
||||
movable_int(movable_int &&other) {
|
||||
valu = other.valu;
|
||||
other.valu = 91;
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
struct functor_builtin_delete {
|
||||
void operator()(T *ptr) { delete ptr; }
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
struct functor_other_delete : functor_builtin_delete<T> {};
|
||||
|
||||
} // namespace helpers
|
||||
|
||||
TEST_CASE("from_raw_ptr_unowned+as_raw_ptr_unowned", "[S]") {
|
||||
static int value = 19;
|
||||
auto hld = smart_holder::from_raw_ptr_unowned(&value);
|
||||
REQUIRE(*hld.as_raw_ptr_unowned<int>() == 19);
|
||||
}
|
||||
|
||||
TEST_CASE("from_raw_ptr_unowned+as_lvalue_ref", "[S]") {
|
||||
static int value = 19;
|
||||
auto hld = smart_holder::from_raw_ptr_unowned(&value);
|
||||
REQUIRE(hld.as_lvalue_ref<int>() == 19);
|
||||
}
|
||||
|
||||
TEST_CASE("from_raw_ptr_unowned+as_rvalue_ref", "[S]") {
|
||||
helpers::movable_int orig(19);
|
||||
{
|
||||
auto hld = smart_holder::from_raw_ptr_unowned(&orig);
|
||||
helpers::movable_int othr(hld.as_rvalue_ref<helpers::movable_int>());
|
||||
REQUIRE(othr.valu == 19);
|
||||
REQUIRE(orig.valu == 91);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("from_raw_ptr_unowned+as_raw_ptr_release_ownership", "[E]") {
|
||||
static int value = 19;
|
||||
auto hld = smart_holder::from_raw_ptr_unowned(&value);
|
||||
REQUIRE_THROWS_WITH(hld.as_raw_ptr_release_ownership<int>(),
|
||||
"Cannot disown non-owning holder (as_raw_ptr_release_ownership).");
|
||||
}
|
||||
|
||||
TEST_CASE("from_raw_ptr_unowned+as_unique_ptr", "[E]") {
|
||||
static int value = 19;
|
||||
auto hld = smart_holder::from_raw_ptr_unowned(&value);
|
||||
REQUIRE_THROWS_WITH(hld.as_unique_ptr<int>(),
|
||||
"Cannot disown non-owning holder (as_unique_ptr).");
|
||||
}
|
||||
|
||||
TEST_CASE("from_raw_ptr_unowned+as_unique_ptr_with_deleter", "[E]") {
|
||||
static int value = 19;
|
||||
auto hld = smart_holder::from_raw_ptr_unowned(&value);
|
||||
REQUIRE_THROWS_WITH((hld.as_unique_ptr<int, helpers::functor_builtin_delete<int>>()),
|
||||
"Missing unique_ptr deleter (as_unique_ptr).");
|
||||
}
|
||||
|
||||
TEST_CASE("from_raw_ptr_unowned+as_shared_ptr", "[S]") {
|
||||
static int value = 19;
|
||||
auto hld = smart_holder::from_raw_ptr_unowned(&value);
|
||||
REQUIRE(*hld.as_shared_ptr<int>() == 19);
|
||||
}
|
||||
|
||||
TEST_CASE("from_raw_ptr_take_ownership+as_lvalue_ref", "[S]") {
|
||||
auto hld = smart_holder::from_raw_ptr_take_ownership(new int(19));
|
||||
REQUIRE(hld.has_pointee());
|
||||
REQUIRE(hld.as_lvalue_ref<int>() == 19);
|
||||
}
|
||||
|
||||
TEST_CASE("from_raw_ptr_take_ownership+as_raw_ptr_release_ownership1", "[S]") {
|
||||
auto hld = smart_holder::from_raw_ptr_take_ownership(new int(19));
|
||||
auto new_owner = std::unique_ptr<int>(hld.as_raw_ptr_release_ownership<int>());
|
||||
REQUIRE(!hld.has_pointee());
|
||||
REQUIRE(*new_owner == 19);
|
||||
}
|
||||
|
||||
TEST_CASE("from_raw_ptr_take_ownership+as_raw_ptr_release_ownership2", "[E]") {
|
||||
auto hld = smart_holder::from_raw_ptr_take_ownership(new int(19));
|
||||
auto shd_ptr = hld.as_shared_ptr<int>();
|
||||
REQUIRE_THROWS_WITH(hld.as_raw_ptr_release_ownership<int>(),
|
||||
"Cannot disown use_count != 1 (as_raw_ptr_release_ownership).");
|
||||
}
|
||||
|
||||
TEST_CASE("from_raw_ptr_take_ownership+as_unique_ptr1", "[S]") {
|
||||
auto hld = smart_holder::from_raw_ptr_take_ownership(new int(19));
|
||||
std::unique_ptr<int> new_owner = hld.as_unique_ptr<int>();
|
||||
REQUIRE(!hld.has_pointee());
|
||||
REQUIRE(*new_owner == 19);
|
||||
}
|
||||
|
||||
TEST_CASE("from_raw_ptr_take_ownership+as_unique_ptr2", "[E]") {
|
||||
auto hld = smart_holder::from_raw_ptr_take_ownership(new int(19));
|
||||
auto shd_ptr = hld.as_shared_ptr<int>();
|
||||
REQUIRE_THROWS_WITH(hld.as_unique_ptr<int>(), "Cannot disown use_count != 1 (as_unique_ptr).");
|
||||
}
|
||||
|
||||
TEST_CASE("from_raw_ptr_take_ownership+as_unique_ptr_with_deleter", "[E]") {
|
||||
auto hld = smart_holder::from_raw_ptr_take_ownership(new int(19));
|
||||
REQUIRE_THROWS_WITH((hld.as_unique_ptr<int, helpers::functor_builtin_delete<int>>()),
|
||||
"Missing unique_ptr deleter (as_unique_ptr).");
|
||||
}
|
||||
|
||||
TEST_CASE("from_raw_ptr_take_ownership+as_shared_ptr", "[S]") {
|
||||
auto hld = smart_holder::from_raw_ptr_take_ownership(new int(19));
|
||||
std::shared_ptr<int> new_owner = hld.as_shared_ptr<int>();
|
||||
REQUIRE(hld.has_pointee());
|
||||
REQUIRE(*new_owner == 19);
|
||||
}
|
||||
|
||||
TEST_CASE("from_unique_ptr+as_lvalue_ref", "[S]") {
|
||||
std::unique_ptr<int> orig_owner(new int(19));
|
||||
auto hld = smart_holder::from_unique_ptr(std::move(orig_owner));
|
||||
REQUIRE(orig_owner.get() == nullptr);
|
||||
REQUIRE(hld.as_lvalue_ref<int>() == 19);
|
||||
}
|
||||
|
||||
TEST_CASE("from_unique_ptr+as_raw_ptr_release_ownership1", "[S]") {
|
||||
std::unique_ptr<int> orig_owner(new int(19));
|
||||
auto hld = smart_holder::from_unique_ptr(std::move(orig_owner));
|
||||
REQUIRE(orig_owner.get() == nullptr);
|
||||
auto new_owner = std::unique_ptr<int>(hld.as_raw_ptr_release_ownership<int>());
|
||||
REQUIRE(!hld.has_pointee());
|
||||
REQUIRE(*new_owner == 19);
|
||||
}
|
||||
|
||||
TEST_CASE("from_unique_ptr+as_raw_ptr_release_ownership2", "[E]") {
|
||||
std::unique_ptr<int> orig_owner(new int(19));
|
||||
auto hld = smart_holder::from_unique_ptr(std::move(orig_owner));
|
||||
REQUIRE(orig_owner.get() == nullptr);
|
||||
auto shd_ptr = hld.as_shared_ptr<int>();
|
||||
REQUIRE_THROWS_WITH(hld.as_raw_ptr_release_ownership<int>(),
|
||||
"Cannot disown use_count != 1 (as_raw_ptr_release_ownership).");
|
||||
}
|
||||
|
||||
TEST_CASE("from_unique_ptr+as_unique_ptr1", "[S]") {
|
||||
std::unique_ptr<int> orig_owner(new int(19));
|
||||
auto hld = smart_holder::from_unique_ptr(std::move(orig_owner));
|
||||
REQUIRE(orig_owner.get() == nullptr);
|
||||
std::unique_ptr<int> new_owner = hld.as_unique_ptr<int>();
|
||||
REQUIRE(!hld.has_pointee());
|
||||
REQUIRE(*new_owner == 19);
|
||||
}
|
||||
|
||||
TEST_CASE("from_unique_ptr+as_unique_ptr2", "[E]") {
|
||||
std::unique_ptr<int> orig_owner(new int(19));
|
||||
auto hld = smart_holder::from_unique_ptr(std::move(orig_owner));
|
||||
REQUIRE(orig_owner.get() == nullptr);
|
||||
auto shd_ptr = hld.as_shared_ptr<int>();
|
||||
REQUIRE_THROWS_WITH(hld.as_unique_ptr<int>(), "Cannot disown use_count != 1 (as_unique_ptr).");
|
||||
}
|
||||
|
||||
TEST_CASE("from_unique_ptr+as_unique_ptr_with_deleter", "[E]") {
|
||||
std::unique_ptr<int> orig_owner(new int(19));
|
||||
auto hld = smart_holder::from_unique_ptr(std::move(orig_owner));
|
||||
REQUIRE(orig_owner.get() == nullptr);
|
||||
REQUIRE_THROWS_WITH((hld.as_unique_ptr<int, helpers::functor_builtin_delete<int>>()),
|
||||
"Incompatible unique_ptr deleter (as_unique_ptr).");
|
||||
}
|
||||
|
||||
TEST_CASE("from_unique_ptr+as_shared_ptr", "[S]") {
|
||||
std::unique_ptr<int> orig_owner(new int(19));
|
||||
auto hld = smart_holder::from_unique_ptr(std::move(orig_owner));
|
||||
REQUIRE(orig_owner.get() == nullptr);
|
||||
std::shared_ptr<int> new_owner = hld.as_shared_ptr<int>();
|
||||
REQUIRE(hld.has_pointee());
|
||||
REQUIRE(*new_owner == 19);
|
||||
}
|
||||
|
||||
TEST_CASE("from_unique_ptr_with_deleter+as_lvalue_ref", "[S]") {
|
||||
std::unique_ptr<int, helpers::functor_builtin_delete<int>> orig_owner(new int(19));
|
||||
auto hld = smart_holder::from_unique_ptr(std::move(orig_owner));
|
||||
REQUIRE(orig_owner.get() == nullptr);
|
||||
REQUIRE(hld.as_lvalue_ref<int>() == 19);
|
||||
}
|
||||
|
||||
TEST_CASE("from_unique_ptr_with_deleter+as_raw_ptr_release_ownership", "[E]") {
|
||||
std::unique_ptr<int, helpers::functor_builtin_delete<int>> orig_owner(new int(19));
|
||||
auto hld = smart_holder::from_unique_ptr(std::move(orig_owner));
|
||||
REQUIRE(orig_owner.get() == nullptr);
|
||||
REQUIRE_THROWS_WITH(hld.as_raw_ptr_release_ownership<int>(),
|
||||
"Cannot disown custom deleter (as_raw_ptr_release_ownership).");
|
||||
}
|
||||
|
||||
TEST_CASE("from_unique_ptr_with_deleter+as_unique_ptr", "[E]") {
|
||||
std::unique_ptr<int, helpers::functor_builtin_delete<int>> orig_owner(new int(19));
|
||||
auto hld = smart_holder::from_unique_ptr(std::move(orig_owner));
|
||||
REQUIRE(orig_owner.get() == nullptr);
|
||||
REQUIRE_THROWS_WITH(hld.as_unique_ptr<int>(),
|
||||
"Incompatible unique_ptr deleter (as_unique_ptr).");
|
||||
}
|
||||
|
||||
TEST_CASE("from_unique_ptr_with_deleter+as_unique_ptr_with_deleter1", "[S]") {
|
||||
std::unique_ptr<int, helpers::functor_builtin_delete<int>> orig_owner(new int(19));
|
||||
auto hld = smart_holder::from_unique_ptr(std::move(orig_owner));
|
||||
REQUIRE(orig_owner.get() == nullptr);
|
||||
std::unique_ptr<int, helpers::functor_builtin_delete<int>> new_owner
|
||||
= hld.as_unique_ptr<int, helpers::functor_builtin_delete<int>>();
|
||||
REQUIRE(!hld.has_pointee());
|
||||
REQUIRE(*new_owner == 19);
|
||||
}
|
||||
|
||||
TEST_CASE("from_unique_ptr_with_deleter+as_unique_ptr_with_deleter2", "[E]") {
|
||||
std::unique_ptr<int, helpers::functor_builtin_delete<int>> orig_owner(new int(19));
|
||||
auto hld = smart_holder::from_unique_ptr(std::move(orig_owner));
|
||||
REQUIRE(orig_owner.get() == nullptr);
|
||||
REQUIRE_THROWS_WITH((hld.as_unique_ptr<int, helpers::functor_other_delete<int>>()),
|
||||
"Incompatible unique_ptr deleter (as_unique_ptr).");
|
||||
}
|
||||
|
||||
TEST_CASE("from_unique_ptr_with_deleter+as_shared_ptr", "[S]") {
|
||||
std::unique_ptr<int, helpers::functor_builtin_delete<int>> orig_owner(new int(19));
|
||||
auto hld = smart_holder::from_unique_ptr(std::move(orig_owner));
|
||||
REQUIRE(orig_owner.get() == nullptr);
|
||||
std::shared_ptr<int> new_owner = hld.as_shared_ptr<int>();
|
||||
REQUIRE(hld.has_pointee());
|
||||
REQUIRE(*new_owner == 19);
|
||||
}
|
||||
|
||||
TEST_CASE("from_shared_ptr+as_lvalue_ref", "[S]") {
|
||||
std::shared_ptr<int> orig_owner(new int(19));
|
||||
auto hld = smart_holder::from_shared_ptr(orig_owner);
|
||||
REQUIRE(hld.as_lvalue_ref<int>() == 19);
|
||||
}
|
||||
|
||||
TEST_CASE("from_shared_ptr+as_raw_ptr_release_ownership", "[E]") {
|
||||
std::shared_ptr<int> orig_owner(new int(19));
|
||||
auto hld = smart_holder::from_shared_ptr(orig_owner);
|
||||
REQUIRE_THROWS_WITH(hld.as_raw_ptr_release_ownership<int>(),
|
||||
"Cannot disown external shared_ptr (as_raw_ptr_release_ownership).");
|
||||
}
|
||||
|
||||
TEST_CASE("from_shared_ptr+as_unique_ptr", "[E]") {
|
||||
std::shared_ptr<int> orig_owner(new int(19));
|
||||
auto hld = smart_holder::from_shared_ptr(orig_owner);
|
||||
REQUIRE_THROWS_WITH(hld.as_unique_ptr<int>(),
|
||||
"Cannot disown external shared_ptr (as_unique_ptr).");
|
||||
}
|
||||
|
||||
TEST_CASE("from_shared_ptr+as_unique_ptr_with_deleter", "[E]") {
|
||||
std::shared_ptr<int> orig_owner(new int(19));
|
||||
auto hld = smart_holder::from_shared_ptr(orig_owner);
|
||||
REQUIRE_THROWS_WITH((hld.as_unique_ptr<int, helpers::functor_builtin_delete<int>>()),
|
||||
"Missing unique_ptr deleter (as_unique_ptr).");
|
||||
}
|
||||
|
||||
TEST_CASE("from_shared_ptr+as_shared_ptr", "[S]") {
|
||||
std::shared_ptr<int> orig_owner(new int(19));
|
||||
auto hld = smart_holder::from_shared_ptr(orig_owner);
|
||||
REQUIRE(*hld.as_shared_ptr<int>() == 19);
|
||||
}
|
||||
|
||||
TEST_CASE("error_unpopulated_holder", "[E]") {
|
||||
smart_holder hld;
|
||||
REQUIRE_THROWS_WITH(hld.as_lvalue_ref<int>(), "Unpopulated holder (as_lvalue_ref).");
|
||||
}
|
||||
|
||||
TEST_CASE("error_disowned_holder", "[E]") {
|
||||
auto hld = smart_holder::from_raw_ptr_take_ownership(new int(19));
|
||||
hld.as_unique_ptr<int>();
|
||||
REQUIRE_THROWS_WITH(hld.as_lvalue_ref<int>(), "Disowned holder (as_lvalue_ref).");
|
||||
}
|
||||
|
||||
TEST_CASE("error_cannot_disown_nullptr", "[E]") {
|
||||
auto hld = smart_holder::from_raw_ptr_take_ownership(new int(19));
|
||||
hld.as_unique_ptr<int>();
|
||||
REQUIRE_THROWS_WITH(hld.as_unique_ptr<int>(), "Cannot disown nullptr (as_unique_ptr).");
|
||||
}
|
@ -23,6 +23,8 @@
|
||||
# pragma warning(disable: 4324) // warning C4324: structure was padded due to alignment specifier
|
||||
#endif
|
||||
|
||||
namespace {
|
||||
|
||||
// test_brace_initialization
|
||||
struct NoBraceInitialization {
|
||||
NoBraceInitialization(std::vector<int> v) : vec{std::move(v)} {}
|
||||
@ -32,6 +34,23 @@ struct NoBraceInitialization {
|
||||
std::vector<int> vec;
|
||||
};
|
||||
|
||||
// test_mismatched_holder
|
||||
struct MismatchBase1 { };
|
||||
struct MismatchDerived1 : MismatchBase1 { };
|
||||
struct MismatchBase2 { };
|
||||
struct MismatchDerived2 : MismatchBase2 { };
|
||||
|
||||
// test_multiple_instances_with_same_pointer
|
||||
struct SamePointer {};
|
||||
|
||||
} // namespace
|
||||
|
||||
PYBIND11_SMART_POINTER_HOLDER_TYPE_CASTERS(MismatchBase1, std::shared_ptr<MismatchBase1>)
|
||||
PYBIND11_SMART_POINTER_HOLDER_TYPE_CASTERS(MismatchDerived1, std::unique_ptr<MismatchDerived1>)
|
||||
PYBIND11_SMART_POINTER_HOLDER_TYPE_CASTERS(MismatchBase2, std::unique_ptr<MismatchBase2>)
|
||||
PYBIND11_SMART_POINTER_HOLDER_TYPE_CASTERS(MismatchDerived2, std::shared_ptr<MismatchDerived2>)
|
||||
PYBIND11_SMART_POINTER_HOLDER_TYPE_CASTERS(SamePointer, std::unique_ptr<SamePointer>)
|
||||
|
||||
TEST_SUBMODULE(class_, m) {
|
||||
// test_instance
|
||||
struct NoConstructor {
|
||||
@ -168,20 +187,15 @@ TEST_SUBMODULE(class_, m) {
|
||||
});
|
||||
|
||||
// test_mismatched_holder
|
||||
struct MismatchBase1 { };
|
||||
struct MismatchDerived1 : MismatchBase1 { };
|
||||
|
||||
struct MismatchBase2 { };
|
||||
struct MismatchDerived2 : MismatchBase2 { };
|
||||
|
||||
m.def("mismatched_holder_1", []() {
|
||||
auto mod = py::module_::import("__main__");
|
||||
py::class_<MismatchBase1, std::shared_ptr<MismatchBase1>>(mod, "MismatchBase1");
|
||||
py::class_<MismatchDerived1, MismatchBase1>(mod, "MismatchDerived1");
|
||||
py::class_<MismatchDerived1, std::unique_ptr<MismatchDerived1>,
|
||||
MismatchBase1>(mod, "MismatchDerived1");
|
||||
});
|
||||
m.def("mismatched_holder_2", []() {
|
||||
auto mod = py::module_::import("__main__");
|
||||
py::class_<MismatchBase2>(mod, "MismatchBase2");
|
||||
py::class_<MismatchBase2, std::unique_ptr<MismatchBase2>>(mod, "MismatchBase2");
|
||||
py::class_<MismatchDerived2, std::shared_ptr<MismatchDerived2>,
|
||||
MismatchBase2>(mod, "MismatchDerived2");
|
||||
});
|
||||
@ -431,7 +445,6 @@ TEST_SUBMODULE(class_, m) {
|
||||
.def("throw_something", &PyPrintDestructor::throw_something);
|
||||
|
||||
// test_multiple_instances_with_same_pointer
|
||||
struct SamePointer {};
|
||||
static SamePointer samePointer;
|
||||
py::class_<SamePointer, std::unique_ptr<SamePointer, py::nodelete>>(m, "SamePointer")
|
||||
.def(py::init([]() { return &samePointer; }));
|
||||
@ -504,7 +517,14 @@ CHECK_BASE(1); CHECK_BASE(2); CHECK_BASE(3); CHECK_BASE(4); CHECK_BASE(5); CHECK
|
||||
CHECK_ALIAS(1); CHECK_ALIAS(2); CHECK_NOALIAS(3); CHECK_ALIAS(4); CHECK_NOALIAS(5); CHECK_ALIAS(6); CHECK_ALIAS(7); CHECK_NOALIAS(8);
|
||||
#define CHECK_HOLDER(N, TYPE) static_assert(std::is_same<typename DoesntBreak##N::holder_type, std::TYPE##_ptr<BreaksBase<N>>>::value, \
|
||||
"DoesntBreak" #N " has wrong holder_type!")
|
||||
CHECK_HOLDER(1, unique); CHECK_HOLDER(2, unique); CHECK_HOLDER(3, unique); CHECK_HOLDER(4, unique); CHECK_HOLDER(5, unique);
|
||||
#define CHECK_SMART_HOLDER(N) static_assert(std::is_same<typename DoesntBreak##N::holder_type, py::smart_holder>::value, \
|
||||
"DoesntBreak" #N " has wrong holder_type!")
|
||||
CHECK_HOLDER(1, unique); CHECK_HOLDER(2, unique); CHECK_HOLDER(3, unique);
|
||||
#ifndef PYBIND11_USE_SMART_HOLDER_AS_DEFAULT
|
||||
CHECK_HOLDER(4, unique); CHECK_HOLDER(5, unique);
|
||||
#else
|
||||
CHECK_SMART_HOLDER(4); CHECK_SMART_HOLDER(5);
|
||||
#endif
|
||||
CHECK_HOLDER(6, shared); CHECK_HOLDER(7, shared); CHECK_HOLDER(8, shared);
|
||||
|
||||
// There's no nice way to test that these fail because they fail to compile; leave them here,
|
||||
|
120
tests/test_class_sh_basic.cpp
Normal file
120
tests/test_class_sh_basic.cpp
Normal file
@ -0,0 +1,120 @@
|
||||
#include "pybind11_tests.h"
|
||||
|
||||
#include <pybind11/smart_holder.h>
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
namespace pybind11_tests {
|
||||
namespace class_sh_basic {
|
||||
|
||||
struct atyp { // Short for "any type".
|
||||
std::string mtxt;
|
||||
atyp() : mtxt("DefaultConstructor") {}
|
||||
atyp(const std::string &mtxt_) : mtxt(mtxt_) {}
|
||||
atyp(const atyp &other) { mtxt = other.mtxt + "_CpCtor"; }
|
||||
atyp(atyp &&other) { mtxt = other.mtxt + "_MvCtor"; }
|
||||
};
|
||||
|
||||
// clang-format off
|
||||
|
||||
atyp rtrn_valu() { atyp obj{"rtrn_valu"}; return obj; }
|
||||
atyp&& rtrn_rref() { static atyp obj; obj.mtxt = "rtrn_rref"; return std::move(obj); }
|
||||
atyp const& rtrn_cref() { static atyp obj; obj.mtxt = "rtrn_cref"; return obj; }
|
||||
atyp& rtrn_mref() { static atyp obj; obj.mtxt = "rtrn_mref"; return obj; }
|
||||
atyp const* rtrn_cptr() { return new atyp{"rtrn_cptr"}; }
|
||||
atyp* rtrn_mptr() { return new atyp{"rtrn_mptr"}; }
|
||||
|
||||
std::string pass_valu(atyp obj) { return "pass_valu:" + obj.mtxt; }
|
||||
std::string pass_cref(atyp const& obj) { return "pass_cref:" + obj.mtxt; }
|
||||
std::string pass_mref(atyp& obj) { return "pass_mref:" + obj.mtxt; }
|
||||
std::string pass_cptr(atyp const* obj) { return "pass_cptr:" + obj->mtxt; }
|
||||
std::string pass_mptr(atyp* obj) { return "pass_mptr:" + obj->mtxt; }
|
||||
|
||||
std::shared_ptr<atyp> rtrn_shmp() { return std::shared_ptr<atyp >(new atyp{"rtrn_shmp"}); }
|
||||
std::shared_ptr<atyp const> rtrn_shcp() { return std::shared_ptr<atyp const>(new atyp{"rtrn_shcp"}); }
|
||||
|
||||
std::string pass_shmp(std::shared_ptr<atyp> obj) { return "pass_shmp:" + obj->mtxt; }
|
||||
std::string pass_shcp(std::shared_ptr<atyp const> obj) { return "pass_shcp:" + obj->mtxt; }
|
||||
|
||||
std::unique_ptr<atyp> rtrn_uqmp() { return std::unique_ptr<atyp >(new atyp{"rtrn_uqmp"}); }
|
||||
std::unique_ptr<atyp const> rtrn_uqcp() { return std::unique_ptr<atyp const>(new atyp{"rtrn_uqcp"}); }
|
||||
|
||||
std::string pass_uqmp(std::unique_ptr<atyp > obj) { return "pass_uqmp:" + obj->mtxt; }
|
||||
std::string pass_uqcp(std::unique_ptr<atyp const> obj) { return "pass_uqcp:" + obj->mtxt; }
|
||||
|
||||
struct sddm : std::default_delete<atyp > {};
|
||||
struct sddc : std::default_delete<atyp const> {};
|
||||
|
||||
std::unique_ptr<atyp, sddm> rtrn_udmp() { return std::unique_ptr<atyp, sddm>(new atyp{"rtrn_udmp"}); }
|
||||
std::unique_ptr<atyp const, sddc> rtrn_udcp() { return std::unique_ptr<atyp const, sddc>(new atyp{"rtrn_udcp"}); }
|
||||
|
||||
std::string pass_udmp(std::unique_ptr<atyp, sddm> obj) { return "pass_udmp:" + obj->mtxt; }
|
||||
std::string pass_udcp(std::unique_ptr<atyp const, sddc> obj) { return "pass_udcp:" + obj->mtxt; }
|
||||
|
||||
// clang-format on
|
||||
|
||||
// Helpers for testing.
|
||||
std::string get_mtxt(atyp const &obj) { return obj.mtxt; }
|
||||
std::unique_ptr<atyp> unique_ptr_roundtrip(std::unique_ptr<atyp> obj) { return obj; }
|
||||
|
||||
} // namespace class_sh_basic
|
||||
} // namespace pybind11_tests
|
||||
|
||||
PYBIND11_SMART_HOLDER_TYPE_CASTERS(pybind11_tests::class_sh_basic::atyp)
|
||||
|
||||
namespace pybind11_tests {
|
||||
namespace class_sh_basic {
|
||||
|
||||
TEST_SUBMODULE(class_sh_basic, m) {
|
||||
namespace py = pybind11;
|
||||
|
||||
py::classh<atyp>(m, "atyp").def(py::init<>()).def(py::init([](const std::string &mtxt) {
|
||||
atyp obj;
|
||||
obj.mtxt = mtxt;
|
||||
return obj;
|
||||
}));
|
||||
|
||||
m.def("rtrn_valu", rtrn_valu);
|
||||
m.def("rtrn_rref", rtrn_rref);
|
||||
m.def("rtrn_cref", rtrn_cref);
|
||||
m.def("rtrn_mref", rtrn_mref);
|
||||
m.def("rtrn_cptr", rtrn_cptr);
|
||||
m.def("rtrn_mptr", rtrn_mptr);
|
||||
|
||||
m.def("pass_valu", pass_valu);
|
||||
m.def("pass_cref", pass_cref);
|
||||
m.def("pass_mref", pass_mref);
|
||||
m.def("pass_cptr", pass_cptr);
|
||||
m.def("pass_mptr", pass_mptr);
|
||||
|
||||
m.def("rtrn_shmp", rtrn_shmp);
|
||||
m.def("rtrn_shcp", rtrn_shcp);
|
||||
|
||||
m.def("pass_shmp", pass_shmp);
|
||||
m.def("pass_shcp", pass_shcp);
|
||||
|
||||
m.def("rtrn_uqmp", rtrn_uqmp);
|
||||
m.def("rtrn_uqcp", rtrn_uqcp);
|
||||
|
||||
m.def("pass_uqmp", pass_uqmp);
|
||||
m.def("pass_uqcp", pass_uqcp);
|
||||
|
||||
m.def("rtrn_udmp", rtrn_udmp);
|
||||
m.def("rtrn_udcp", rtrn_udcp);
|
||||
|
||||
m.def("pass_udmp", pass_udmp);
|
||||
m.def("pass_udcp", pass_udcp);
|
||||
|
||||
// Helpers for testing.
|
||||
// These require selected functions above to work first, as indicated:
|
||||
m.def("get_mtxt", get_mtxt); // pass_cref
|
||||
m.def("unique_ptr_roundtrip", unique_ptr_roundtrip); // pass_uqmp, rtrn_uqmp
|
||||
|
||||
m.def("py_type_handle_of_atyp", []() {
|
||||
return py::type::handle_of<atyp>(); // Exercises static_cast in this function.
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace class_sh_basic
|
||||
} // namespace pybind11_tests
|
103
tests/test_class_sh_basic.py
Normal file
103
tests/test_class_sh_basic.py
Normal file
@ -0,0 +1,103 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Importing re before pytest after observing a PyPy CI flake when importing pytest first.
|
||||
import re
|
||||
|
||||
import pytest
|
||||
|
||||
from pybind11_tests import class_sh_basic as m
|
||||
|
||||
|
||||
def test_atyp_constructors():
|
||||
obj = m.atyp()
|
||||
assert obj.__class__.__name__ == "atyp"
|
||||
obj = m.atyp("")
|
||||
assert obj.__class__.__name__ == "atyp"
|
||||
obj = m.atyp("txtm")
|
||||
assert obj.__class__.__name__ == "atyp"
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"rtrn_f, expected",
|
||||
[
|
||||
(m.rtrn_valu, "rtrn_valu(_MvCtor)*_MvCtor"),
|
||||
(m.rtrn_rref, "rtrn_rref(_MvCtor)*_MvCtor"),
|
||||
(m.rtrn_cref, "rtrn_cref(_MvCtor)*_CpCtor"),
|
||||
(m.rtrn_mref, "rtrn_mref(_MvCtor)*_CpCtor"),
|
||||
(m.rtrn_cptr, "rtrn_cptr"),
|
||||
(m.rtrn_mptr, "rtrn_mptr"),
|
||||
(m.rtrn_shmp, "rtrn_shmp"),
|
||||
(m.rtrn_shcp, "rtrn_shcp"),
|
||||
(m.rtrn_uqmp, "rtrn_uqmp"),
|
||||
(m.rtrn_uqcp, "rtrn_uqcp"),
|
||||
(m.rtrn_udmp, "rtrn_udmp"),
|
||||
(m.rtrn_udcp, "rtrn_udcp"),
|
||||
],
|
||||
)
|
||||
def test_cast(rtrn_f, expected):
|
||||
assert re.match(expected, m.get_mtxt(rtrn_f()))
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"pass_f, mtxt, expected",
|
||||
[
|
||||
(m.pass_valu, "Valu", "pass_valu:Valu(_MvCtor)*_CpCtor"),
|
||||
(m.pass_cref, "Cref", "pass_cref:Cref(_MvCtor)*_MvCtor"),
|
||||
(m.pass_mref, "Mref", "pass_mref:Mref(_MvCtor)*_MvCtor"),
|
||||
(m.pass_cptr, "Cptr", "pass_cptr:Cptr(_MvCtor)*_MvCtor"),
|
||||
(m.pass_mptr, "Mptr", "pass_mptr:Mptr(_MvCtor)*_MvCtor"),
|
||||
(m.pass_shmp, "Shmp", "pass_shmp:Shmp(_MvCtor)*_MvCtor"),
|
||||
(m.pass_shcp, "Shcp", "pass_shcp:Shcp(_MvCtor)*_MvCtor"),
|
||||
(m.pass_uqmp, "Uqmp", "pass_uqmp:Uqmp(_MvCtor)*_MvCtor"),
|
||||
(m.pass_uqcp, "Uqcp", "pass_uqcp:Uqcp(_MvCtor)*_MvCtor"),
|
||||
],
|
||||
)
|
||||
def test_load_with_mtxt(pass_f, mtxt, expected):
|
||||
assert re.match(expected, pass_f(m.atyp(mtxt)))
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"pass_f, rtrn_f, expected",
|
||||
[
|
||||
(m.pass_udmp, m.rtrn_udmp, "pass_udmp:rtrn_udmp"),
|
||||
(m.pass_udcp, m.rtrn_udcp, "pass_udcp:rtrn_udcp"),
|
||||
],
|
||||
)
|
||||
def test_load_with_rtrn_f(pass_f, rtrn_f, expected):
|
||||
assert pass_f(rtrn_f()) == expected
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"pass_f, rtrn_f, expected",
|
||||
[
|
||||
(m.pass_uqmp, m.rtrn_uqmp, "pass_uqmp:rtrn_uqmp"),
|
||||
(m.pass_uqcp, m.rtrn_uqcp, "pass_uqcp:rtrn_uqcp"),
|
||||
(m.pass_udmp, m.rtrn_udmp, "pass_udmp:rtrn_udmp"),
|
||||
(m.pass_udcp, m.rtrn_udcp, "pass_udcp:rtrn_udcp"),
|
||||
],
|
||||
)
|
||||
def test_pass_unique_ptr_disowns(pass_f, rtrn_f, expected):
|
||||
obj = rtrn_f()
|
||||
assert pass_f(obj) == expected
|
||||
with pytest.raises(RuntimeError) as exc_info:
|
||||
pass_f(obj)
|
||||
assert str(exc_info.value) == (
|
||||
"Missing value for wrapped C++ type: Python instance was disowned."
|
||||
)
|
||||
|
||||
|
||||
def test_unique_ptr_roundtrip(num_round_trips=1000):
|
||||
# Multiple roundtrips to stress-test instance registration/deregistration.
|
||||
recycled = m.atyp("passenger")
|
||||
for _ in range(num_round_trips):
|
||||
id_orig = id(recycled)
|
||||
recycled = m.unique_ptr_roundtrip(recycled)
|
||||
assert re.match("passenger(_MvCtor)*_MvCtor", m.get_mtxt(recycled))
|
||||
id_rtrn = id(recycled)
|
||||
# Ensure the returned object is a different Python instance.
|
||||
assert id_rtrn != id_orig
|
||||
id_orig = id_rtrn
|
||||
|
||||
|
||||
def test_py_type_handle_of_atyp():
|
||||
obj = m.py_type_handle_of_atyp()
|
||||
assert obj.__class__.__name__ == "pybind11_type"
|
180
tests/test_class_sh_factory_constructors.cpp
Normal file
180
tests/test_class_sh_factory_constructors.cpp
Normal file
@ -0,0 +1,180 @@
|
||||
#include "pybind11_tests.h"
|
||||
|
||||
#include <pybind11/smart_holder.h>
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
namespace pybind11_tests {
|
||||
namespace test_class_sh_factory_constructors {
|
||||
|
||||
template <int> // Using int as a trick to easily generate a series of types.
|
||||
struct atyp { // Short for "any type".
|
||||
std::string mtxt;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
std::string get_mtxt(const T &obj) {
|
||||
return obj.mtxt;
|
||||
}
|
||||
|
||||
using atyp_valu = atyp<0x0>;
|
||||
using atyp_rref = atyp<0x1>;
|
||||
using atyp_cref = atyp<0x2>;
|
||||
using atyp_mref = atyp<0x3>;
|
||||
using atyp_cptr = atyp<0x4>;
|
||||
using atyp_mptr = atyp<0x5>;
|
||||
using atyp_shmp = atyp<0x6>;
|
||||
using atyp_shcp = atyp<0x7>;
|
||||
using atyp_uqmp = atyp<0x8>;
|
||||
using atyp_uqcp = atyp<0x9>;
|
||||
using atyp_udmp = atyp<0xA>;
|
||||
using atyp_udcp = atyp<0xB>;
|
||||
|
||||
// clang-format off
|
||||
|
||||
atyp_valu rtrn_valu() { atyp_valu obj{"Valu"}; return obj; }
|
||||
atyp_rref&& rtrn_rref() { static atyp_rref obj; obj.mtxt = "Rref"; return std::move(obj); }
|
||||
atyp_cref const& rtrn_cref() { static atyp_cref obj; obj.mtxt = "Cref"; return obj; }
|
||||
atyp_mref& rtrn_mref() { static atyp_mref obj; obj.mtxt = "Mref"; return obj; }
|
||||
atyp_cptr const* rtrn_cptr() { return new atyp_cptr{"Cptr"}; }
|
||||
atyp_mptr* rtrn_mptr() { return new atyp_mptr{"Mptr"}; }
|
||||
|
||||
std::shared_ptr<atyp_shmp> rtrn_shmp() { return std::shared_ptr<atyp_shmp >(new atyp_shmp{"Shmp"}); }
|
||||
std::shared_ptr<atyp_shcp const> rtrn_shcp() { return std::shared_ptr<atyp_shcp const>(new atyp_shcp{"Shcp"}); }
|
||||
|
||||
std::unique_ptr<atyp_uqmp> rtrn_uqmp() { return std::unique_ptr<atyp_uqmp >(new atyp_uqmp{"Uqmp"}); }
|
||||
std::unique_ptr<atyp_uqcp const> rtrn_uqcp() { return std::unique_ptr<atyp_uqcp const>(new atyp_uqcp{"Uqcp"}); }
|
||||
|
||||
struct sddm : std::default_delete<atyp_udmp > {};
|
||||
struct sddc : std::default_delete<atyp_udcp const> {};
|
||||
|
||||
std::unique_ptr<atyp_udmp, sddm> rtrn_udmp() { return std::unique_ptr<atyp_udmp, sddm>(new atyp_udmp{"Udmp"}); }
|
||||
std::unique_ptr<atyp_udcp const, sddc> rtrn_udcp() { return std::unique_ptr<atyp_udcp const, sddc>(new atyp_udcp{"Udcp"}); }
|
||||
|
||||
// clang-format on
|
||||
|
||||
// Minimalistic approach to achieve full coverage of construct() overloads for constructing
|
||||
// smart_holder from unique_ptr and shared_ptr returns.
|
||||
struct with_alias {
|
||||
int val = 0;
|
||||
virtual ~with_alias() = default;
|
||||
// Some compilers complain about implicitly defined versions of some of the following:
|
||||
with_alias() = default;
|
||||
with_alias(const with_alias &) = default;
|
||||
with_alias(with_alias &&) = default;
|
||||
with_alias &operator=(const with_alias &) = default;
|
||||
with_alias &operator=(with_alias &&) = default;
|
||||
};
|
||||
struct with_alias_alias : with_alias {};
|
||||
struct sddwaa : std::default_delete<with_alias_alias> {};
|
||||
|
||||
} // namespace test_class_sh_factory_constructors
|
||||
} // namespace pybind11_tests
|
||||
|
||||
PYBIND11_SMART_HOLDER_TYPE_CASTERS(pybind11_tests::test_class_sh_factory_constructors::atyp_valu)
|
||||
PYBIND11_SMART_HOLDER_TYPE_CASTERS(pybind11_tests::test_class_sh_factory_constructors::atyp_rref)
|
||||
PYBIND11_SMART_HOLDER_TYPE_CASTERS(pybind11_tests::test_class_sh_factory_constructors::atyp_cref)
|
||||
PYBIND11_SMART_HOLDER_TYPE_CASTERS(pybind11_tests::test_class_sh_factory_constructors::atyp_mref)
|
||||
PYBIND11_SMART_HOLDER_TYPE_CASTERS(pybind11_tests::test_class_sh_factory_constructors::atyp_cptr)
|
||||
PYBIND11_SMART_HOLDER_TYPE_CASTERS(pybind11_tests::test_class_sh_factory_constructors::atyp_mptr)
|
||||
PYBIND11_SMART_HOLDER_TYPE_CASTERS(pybind11_tests::test_class_sh_factory_constructors::atyp_shmp)
|
||||
PYBIND11_SMART_HOLDER_TYPE_CASTERS(pybind11_tests::test_class_sh_factory_constructors::atyp_shcp)
|
||||
PYBIND11_SMART_HOLDER_TYPE_CASTERS(pybind11_tests::test_class_sh_factory_constructors::atyp_uqmp)
|
||||
PYBIND11_SMART_HOLDER_TYPE_CASTERS(pybind11_tests::test_class_sh_factory_constructors::atyp_uqcp)
|
||||
PYBIND11_SMART_HOLDER_TYPE_CASTERS(pybind11_tests::test_class_sh_factory_constructors::atyp_udmp)
|
||||
PYBIND11_SMART_HOLDER_TYPE_CASTERS(pybind11_tests::test_class_sh_factory_constructors::atyp_udcp)
|
||||
PYBIND11_SMART_HOLDER_TYPE_CASTERS(pybind11_tests::test_class_sh_factory_constructors::with_alias)
|
||||
|
||||
TEST_SUBMODULE(class_sh_factory_constructors, m) {
|
||||
using namespace pybind11_tests::test_class_sh_factory_constructors;
|
||||
|
||||
py::classh<atyp_valu>(m, "atyp_valu")
|
||||
.def(py::init(&rtrn_valu))
|
||||
.def("get_mtxt", get_mtxt<atyp_valu>);
|
||||
|
||||
py::classh<atyp_rref>(m, "atyp_rref")
|
||||
.def(py::init(&rtrn_rref))
|
||||
.def("get_mtxt", get_mtxt<atyp_rref>);
|
||||
|
||||
py::classh<atyp_cref>(m, "atyp_cref")
|
||||
// class_: ... must return a compatible ...
|
||||
// classh: ... cannot pass object of non-trivial type ...
|
||||
// .def(py::init(&rtrn_cref))
|
||||
.def("get_mtxt", get_mtxt<atyp_cref>);
|
||||
|
||||
py::classh<atyp_mref>(m, "atyp_mref")
|
||||
// class_: ... must return a compatible ...
|
||||
// classh: ... cannot pass object of non-trivial type ...
|
||||
// .def(py::init(&rtrn_mref))
|
||||
.def("get_mtxt", get_mtxt<atyp_mref>);
|
||||
|
||||
py::classh<atyp_cptr>(m, "atyp_cptr")
|
||||
// class_: ... must return a compatible ...
|
||||
// classh: ... must return a compatible ...
|
||||
// .def(py::init(&rtrn_cptr))
|
||||
.def("get_mtxt", get_mtxt<atyp_cptr>);
|
||||
|
||||
py::classh<atyp_mptr>(m, "atyp_mptr")
|
||||
.def(py::init(&rtrn_mptr))
|
||||
.def("get_mtxt", get_mtxt<atyp_mptr>);
|
||||
|
||||
py::classh<atyp_shmp>(m, "atyp_shmp")
|
||||
.def(py::init(&rtrn_shmp))
|
||||
.def("get_mtxt", get_mtxt<atyp_shmp>);
|
||||
|
||||
py::classh<atyp_shcp>(m, "atyp_shcp")
|
||||
// py::class_<atyp_shcp, std::shared_ptr<atyp_shcp>>(m, "atyp_shcp")
|
||||
// class_: ... must return a compatible ...
|
||||
// classh: ... cannot pass object of non-trivial type ...
|
||||
// .def(py::init(&rtrn_shcp))
|
||||
.def("get_mtxt", get_mtxt<atyp_shcp>);
|
||||
|
||||
py::classh<atyp_uqmp>(m, "atyp_uqmp")
|
||||
.def(py::init(&rtrn_uqmp))
|
||||
.def("get_mtxt", get_mtxt<atyp_uqmp>);
|
||||
|
||||
py::classh<atyp_uqcp>(m, "atyp_uqcp")
|
||||
// class_: ... cannot pass object of non-trivial type ...
|
||||
// classh: ... cannot pass object of non-trivial type ...
|
||||
// .def(py::init(&rtrn_uqcp))
|
||||
.def("get_mtxt", get_mtxt<atyp_uqcp>);
|
||||
|
||||
py::classh<atyp_udmp>(m, "atyp_udmp")
|
||||
.def(py::init(&rtrn_udmp))
|
||||
.def("get_mtxt", get_mtxt<atyp_udmp>);
|
||||
|
||||
py::classh<atyp_udcp>(m, "atyp_udcp")
|
||||
// py::class_<atyp_udcp, std::unique_ptr<atyp_udcp, sddc>>(m, "atyp_udcp")
|
||||
// class_: ... must return a compatible ...
|
||||
// classh: ... cannot pass object of non-trivial type ...
|
||||
// .def(py::init(&rtrn_udcp))
|
||||
.def("get_mtxt", get_mtxt<atyp_udcp>);
|
||||
|
||||
py::classh<with_alias, with_alias_alias>(m, "with_alias")
|
||||
.def_readonly("val", &with_alias::val)
|
||||
.def(py::init([](int i) {
|
||||
auto p = std::unique_ptr<with_alias_alias, sddwaa>(new with_alias_alias);
|
||||
p->val = i * 100;
|
||||
return p;
|
||||
}))
|
||||
.def(py::init([](int i, int j) {
|
||||
auto p = std::unique_ptr<with_alias_alias>(new with_alias_alias);
|
||||
p->val = i * 100 + j * 10;
|
||||
return p;
|
||||
}))
|
||||
.def(py::init([](int i, int j, int k) {
|
||||
auto p = std::shared_ptr<with_alias_alias>(new with_alias_alias);
|
||||
p->val = i * 100 + j * 10 + k;
|
||||
return p;
|
||||
}))
|
||||
.def(py::init(
|
||||
[](int, int, int, int) { return std::unique_ptr<with_alias>(new with_alias); },
|
||||
[](int, int, int, int) {
|
||||
return std::unique_ptr<with_alias>(new with_alias); // Invalid alias factory.
|
||||
}))
|
||||
.def(py::init([](int, int, int, int, int) { return std::make_shared<with_alias>(); },
|
||||
[](int, int, int, int, int) {
|
||||
return std::make_shared<with_alias>(); // Invalid alias factory.
|
||||
}));
|
||||
}
|
52
tests/test_class_sh_factory_constructors.py
Normal file
52
tests/test_class_sh_factory_constructors.py
Normal file
@ -0,0 +1,52 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import pytest
|
||||
|
||||
from pybind11_tests import class_sh_factory_constructors as m
|
||||
|
||||
|
||||
def test_atyp_factories():
|
||||
assert m.atyp_valu().get_mtxt() == "Valu"
|
||||
assert m.atyp_rref().get_mtxt() == "Rref"
|
||||
# sert m.atyp_cref().get_mtxt() == "Cref"
|
||||
# sert m.atyp_mref().get_mtxt() == "Mref"
|
||||
# sert m.atyp_cptr().get_mtxt() == "Cptr"
|
||||
assert m.atyp_mptr().get_mtxt() == "Mptr"
|
||||
assert m.atyp_shmp().get_mtxt() == "Shmp"
|
||||
# sert m.atyp_shcp().get_mtxt() == "Shcp"
|
||||
assert m.atyp_uqmp().get_mtxt() == "Uqmp"
|
||||
# sert m.atyp_uqcp().get_mtxt() == "Uqcp"
|
||||
assert m.atyp_udmp().get_mtxt() == "Udmp"
|
||||
# sert m.atyp_udcp().get_mtxt() == "Udcp"
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"init_args, expected",
|
||||
[
|
||||
((3,), 300),
|
||||
((5, 7), 570),
|
||||
((9, 11, 13), 1023),
|
||||
],
|
||||
)
|
||||
def test_with_alias_success(init_args, expected):
|
||||
assert m.with_alias(*init_args).val == expected
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"num_init_args, smart_ptr",
|
||||
[
|
||||
(4, "std::unique_ptr"),
|
||||
(5, "std::shared_ptr"),
|
||||
],
|
||||
)
|
||||
def test_with_alias_invalid(num_init_args, smart_ptr):
|
||||
class PyDrvdWithAlias(m.with_alias):
|
||||
pass
|
||||
|
||||
with pytest.raises(TypeError) as excinfo:
|
||||
PyDrvdWithAlias(*((0,) * num_init_args))
|
||||
assert (
|
||||
str(excinfo.value)
|
||||
== "pybind11::init(): construction failed: returned "
|
||||
+ smart_ptr
|
||||
+ " pointee is not an alias instance"
|
||||
)
|
105
tests/test_class_sh_inheritance.cpp
Normal file
105
tests/test_class_sh_inheritance.cpp
Normal file
@ -0,0 +1,105 @@
|
||||
#include "pybind11_tests.h"
|
||||
|
||||
#include <pybind11/smart_holder.h>
|
||||
|
||||
#include <memory>
|
||||
|
||||
namespace pybind11_tests {
|
||||
namespace class_sh_inheritance {
|
||||
|
||||
template <int Id>
|
||||
struct base_template {
|
||||
base_template() : base_id(Id) {}
|
||||
virtual ~base_template() = default;
|
||||
virtual int id() const { return base_id; }
|
||||
int base_id;
|
||||
|
||||
// Some compilers complain about implicitly defined versions of some of the following:
|
||||
base_template(const base_template &) = default;
|
||||
base_template(base_template &&) = default;
|
||||
base_template &operator=(const base_template &) = default;
|
||||
base_template &operator=(base_template &&) = default;
|
||||
};
|
||||
|
||||
using base = base_template<100>;
|
||||
|
||||
struct drvd : base {
|
||||
int id() const override { return 2 * base_id; }
|
||||
};
|
||||
|
||||
// clang-format off
|
||||
inline drvd *rtrn_mptr_drvd() { return new drvd; }
|
||||
inline base *rtrn_mptr_drvd_up_cast() { return new drvd; }
|
||||
|
||||
inline int pass_cptr_base(base const *b) { return b->id() + 11; }
|
||||
inline int pass_cptr_drvd(drvd const *d) { return d->id() + 12; }
|
||||
|
||||
inline std::shared_ptr<drvd> rtrn_shmp_drvd() { return std::shared_ptr<drvd>(new drvd); }
|
||||
inline std::shared_ptr<base> rtrn_shmp_drvd_up_cast() { return std::shared_ptr<drvd>(new drvd); }
|
||||
|
||||
inline int pass_shcp_base(std::shared_ptr<base const> b) { return b->id() + 21; }
|
||||
inline int pass_shcp_drvd(std::shared_ptr<drvd const> d) { return d->id() + 22; }
|
||||
// clang-format on
|
||||
|
||||
using base1 = base_template<110>;
|
||||
using base2 = base_template<120>;
|
||||
|
||||
// Not reusing base here because it would interfere with the single-inheritance test.
|
||||
struct drvd2 : base1, base2 {
|
||||
int id() const override { return 3 * base1::base_id + 4 * base2::base_id; }
|
||||
};
|
||||
|
||||
// clang-format off
|
||||
inline drvd2 *rtrn_mptr_drvd2() { return new drvd2; }
|
||||
inline base1 *rtrn_mptr_drvd2_up_cast1() { return new drvd2; }
|
||||
inline base2 *rtrn_mptr_drvd2_up_cast2() { return new drvd2; }
|
||||
|
||||
inline int pass_cptr_base1(base1 const *b) { return b->id() + 21; }
|
||||
inline int pass_cptr_base2(base2 const *b) { return b->id() + 22; }
|
||||
inline int pass_cptr_drvd2(drvd2 const *d) { return d->id() + 23; }
|
||||
// clang-format on
|
||||
|
||||
} // namespace class_sh_inheritance
|
||||
} // namespace pybind11_tests
|
||||
|
||||
PYBIND11_SMART_HOLDER_TYPE_CASTERS(pybind11_tests::class_sh_inheritance::base)
|
||||
PYBIND11_SMART_HOLDER_TYPE_CASTERS(pybind11_tests::class_sh_inheritance::drvd)
|
||||
|
||||
PYBIND11_SMART_HOLDER_TYPE_CASTERS(pybind11_tests::class_sh_inheritance::base1)
|
||||
PYBIND11_SMART_HOLDER_TYPE_CASTERS(pybind11_tests::class_sh_inheritance::base2)
|
||||
PYBIND11_SMART_HOLDER_TYPE_CASTERS(pybind11_tests::class_sh_inheritance::drvd2)
|
||||
|
||||
namespace pybind11_tests {
|
||||
namespace class_sh_inheritance {
|
||||
|
||||
TEST_SUBMODULE(class_sh_inheritance, m) {
|
||||
py::classh<base>(m, "base");
|
||||
py::classh<drvd, base>(m, "drvd");
|
||||
|
||||
auto rvto = py::return_value_policy::take_ownership;
|
||||
|
||||
m.def("rtrn_mptr_drvd", rtrn_mptr_drvd, rvto);
|
||||
m.def("rtrn_mptr_drvd_up_cast", rtrn_mptr_drvd_up_cast, rvto);
|
||||
m.def("pass_cptr_base", pass_cptr_base);
|
||||
m.def("pass_cptr_drvd", pass_cptr_drvd);
|
||||
|
||||
m.def("rtrn_shmp_drvd", rtrn_shmp_drvd);
|
||||
m.def("rtrn_shmp_drvd_up_cast", rtrn_shmp_drvd_up_cast);
|
||||
m.def("pass_shcp_base", pass_shcp_base);
|
||||
m.def("pass_shcp_drvd", pass_shcp_drvd);
|
||||
|
||||
// __init__ needed for Python inheritance.
|
||||
py::classh<base1>(m, "base1").def(py::init<>());
|
||||
py::classh<base2>(m, "base2").def(py::init<>());
|
||||
py::classh<drvd2, base1, base2>(m, "drvd2");
|
||||
|
||||
m.def("rtrn_mptr_drvd2", rtrn_mptr_drvd2, rvto);
|
||||
m.def("rtrn_mptr_drvd2_up_cast1", rtrn_mptr_drvd2_up_cast1, rvto);
|
||||
m.def("rtrn_mptr_drvd2_up_cast2", rtrn_mptr_drvd2_up_cast2, rvto);
|
||||
m.def("pass_cptr_base1", pass_cptr_base1);
|
||||
m.def("pass_cptr_base2", pass_cptr_base2);
|
||||
m.def("pass_cptr_drvd2", pass_cptr_drvd2);
|
||||
}
|
||||
|
||||
} // namespace class_sh_inheritance
|
||||
} // namespace pybind11_tests
|
63
tests/test_class_sh_inheritance.py
Normal file
63
tests/test_class_sh_inheritance.py
Normal file
@ -0,0 +1,63 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from pybind11_tests import class_sh_inheritance as m
|
||||
|
||||
|
||||
def test_rtrn_mptr_drvd_pass_cptr_base():
|
||||
d = m.rtrn_mptr_drvd()
|
||||
i = m.pass_cptr_base(d) # load_impl Case 2a
|
||||
assert i == 2 * 100 + 11
|
||||
|
||||
|
||||
def test_rtrn_shmp_drvd_pass_shcp_base():
|
||||
d = m.rtrn_shmp_drvd()
|
||||
i = m.pass_shcp_base(d) # load_impl Case 2a
|
||||
assert i == 2 * 100 + 21
|
||||
|
||||
|
||||
def test_rtrn_mptr_drvd_up_cast_pass_cptr_drvd():
|
||||
b = m.rtrn_mptr_drvd_up_cast()
|
||||
# the base return is down-cast immediately.
|
||||
assert b.__class__.__name__ == "drvd"
|
||||
i = m.pass_cptr_drvd(b)
|
||||
assert i == 2 * 100 + 12
|
||||
|
||||
|
||||
def test_rtrn_shmp_drvd_up_cast_pass_shcp_drvd():
|
||||
b = m.rtrn_shmp_drvd_up_cast()
|
||||
# the base return is down-cast immediately.
|
||||
assert b.__class__.__name__ == "drvd"
|
||||
i = m.pass_shcp_drvd(b)
|
||||
assert i == 2 * 100 + 22
|
||||
|
||||
|
||||
def test_rtrn_mptr_drvd2_pass_cptr_bases():
|
||||
d = m.rtrn_mptr_drvd2()
|
||||
i1 = m.pass_cptr_base1(d) # load_impl Case 2c
|
||||
assert i1 == 3 * 110 + 4 * 120 + 21
|
||||
i2 = m.pass_cptr_base2(d)
|
||||
assert i2 == 3 * 110 + 4 * 120 + 22
|
||||
|
||||
|
||||
def test_rtrn_mptr_drvd2_up_casts_pass_cptr_drvd2():
|
||||
b1 = m.rtrn_mptr_drvd2_up_cast1()
|
||||
assert b1.__class__.__name__ == "drvd2"
|
||||
i1 = m.pass_cptr_drvd2(b1)
|
||||
assert i1 == 3 * 110 + 4 * 120 + 23
|
||||
b2 = m.rtrn_mptr_drvd2_up_cast2()
|
||||
assert b2.__class__.__name__ == "drvd2"
|
||||
i2 = m.pass_cptr_drvd2(b2)
|
||||
assert i2 == 3 * 110 + 4 * 120 + 23
|
||||
|
||||
|
||||
def test_python_drvd2():
|
||||
class Drvd2(m.base1, m.base2):
|
||||
def __init__(self):
|
||||
m.base1.__init__(self)
|
||||
m.base2.__init__(self)
|
||||
|
||||
d = Drvd2()
|
||||
i1 = m.pass_cptr_base1(d) # load_impl Case 2b
|
||||
assert i1 == 110 + 21
|
||||
i2 = m.pass_cptr_base2(d)
|
||||
assert i2 == 120 + 22
|
27
tests/test_class_sh_module_local.py
Normal file
27
tests/test_class_sh_module_local.py
Normal file
@ -0,0 +1,27 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import pytest
|
||||
|
||||
import class_sh_module_local_0 as m0
|
||||
import class_sh_module_local_1 as m1
|
||||
import class_sh_module_local_2 as m2
|
||||
|
||||
|
||||
def test_cross_module_get_mtxt():
|
||||
obj1 = m1.atyp("A")
|
||||
assert obj1.tag() == 1
|
||||
obj2 = m2.atyp("B")
|
||||
assert obj2.tag() == 2
|
||||
assert m1.get_mtxt(obj1) == "A"
|
||||
assert m2.get_mtxt(obj2) == "B"
|
||||
assert m1.get_mtxt(obj2) == "B"
|
||||
assert m2.get_mtxt(obj1) == "A"
|
||||
assert m0.get_mtxt(obj1) == "A"
|
||||
assert m0.get_mtxt(obj2) == "B"
|
||||
|
||||
|
||||
def test_m0_rtrn_valu_atyp():
|
||||
with pytest.raises(TypeError) as exc_info:
|
||||
m0.rtrn_valu_atyp()
|
||||
assert str(exc_info.value).startswith(
|
||||
"Unable to convert function return value to a Python type!"
|
||||
)
|
61
tests/test_class_sh_unique_ptr_member.cpp
Normal file
61
tests/test_class_sh_unique_ptr_member.cpp
Normal file
@ -0,0 +1,61 @@
|
||||
#include "pybind11_tests.h"
|
||||
|
||||
#include <pybind11/smart_holder.h>
|
||||
|
||||
#include <memory>
|
||||
|
||||
namespace pybind11_tests {
|
||||
namespace class_sh_unique_ptr_member {
|
||||
|
||||
class pointee { // NOT copyable.
|
||||
public:
|
||||
pointee() = default;
|
||||
|
||||
int get_int() const { return 213; }
|
||||
|
||||
private:
|
||||
pointee(const pointee &) = delete;
|
||||
pointee(pointee &&) = delete;
|
||||
pointee &operator=(const pointee &) = delete;
|
||||
pointee &operator=(pointee &&) = delete;
|
||||
};
|
||||
|
||||
inline std::unique_ptr<pointee> make_unique_pointee() {
|
||||
return std::unique_ptr<pointee>(new pointee);
|
||||
}
|
||||
|
||||
class ptr_owner {
|
||||
public:
|
||||
explicit ptr_owner(std::unique_ptr<pointee> ptr) : ptr_(std::move(ptr)) {}
|
||||
|
||||
bool is_owner() const { return bool(ptr_); }
|
||||
|
||||
std::unique_ptr<pointee> give_up_ownership_via_unique_ptr() { return std::move(ptr_); }
|
||||
std::shared_ptr<pointee> give_up_ownership_via_shared_ptr() { return std::move(ptr_); }
|
||||
|
||||
private:
|
||||
std::unique_ptr<pointee> ptr_;
|
||||
};
|
||||
|
||||
} // namespace class_sh_unique_ptr_member
|
||||
} // namespace pybind11_tests
|
||||
|
||||
PYBIND11_SMART_HOLDER_TYPE_CASTERS(pybind11_tests::class_sh_unique_ptr_member::pointee)
|
||||
|
||||
namespace pybind11_tests {
|
||||
namespace class_sh_unique_ptr_member {
|
||||
|
||||
TEST_SUBMODULE(class_sh_unique_ptr_member, m) {
|
||||
py::classh<pointee>(m, "pointee").def(py::init<>()).def("get_int", &pointee::get_int);
|
||||
|
||||
m.def("make_unique_pointee", make_unique_pointee);
|
||||
|
||||
py::class_<ptr_owner>(m, "ptr_owner")
|
||||
.def(py::init<std::unique_ptr<pointee>>(), py::arg("ptr"))
|
||||
.def("is_owner", &ptr_owner::is_owner)
|
||||
.def("give_up_ownership_via_unique_ptr", &ptr_owner::give_up_ownership_via_unique_ptr)
|
||||
.def("give_up_ownership_via_shared_ptr", &ptr_owner::give_up_ownership_via_shared_ptr);
|
||||
}
|
||||
|
||||
} // namespace class_sh_unique_ptr_member
|
||||
} // namespace pybind11_tests
|
29
tests/test_class_sh_unique_ptr_member.py
Normal file
29
tests/test_class_sh_unique_ptr_member.py
Normal file
@ -0,0 +1,29 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import pytest
|
||||
|
||||
from pybind11_tests import class_sh_unique_ptr_member as m
|
||||
|
||||
|
||||
def test_make_unique_pointee():
|
||||
obj = m.make_unique_pointee()
|
||||
assert obj.get_int() == 213
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"give_up_ownership_via",
|
||||
["give_up_ownership_via_unique_ptr", "give_up_ownership_via_shared_ptr"],
|
||||
)
|
||||
def test_pointee_and_ptr_owner(give_up_ownership_via):
|
||||
obj = m.pointee()
|
||||
assert obj.get_int() == 213
|
||||
owner = m.ptr_owner(obj)
|
||||
with pytest.raises(RuntimeError) as exc_info:
|
||||
obj.get_int()
|
||||
assert (
|
||||
str(exc_info.value)
|
||||
== "Missing value for wrapped C++ type: Python instance was disowned."
|
||||
)
|
||||
assert owner.is_owner()
|
||||
reclaimed = getattr(owner, give_up_ownership_via)()
|
||||
assert not owner.is_owner()
|
||||
assert reclaimed.get_int() == 213
|
@ -139,6 +139,11 @@ public:
|
||||
static std::shared_ptr<TestFactory3> construct3(int a) { return std::shared_ptr<TestFactory3>(new TestFactory3(a)); }
|
||||
};
|
||||
|
||||
PYBIND11_SMART_POINTER_HOLDER_TYPE_CASTERS(TestFactory3, std::shared_ptr<TestFactory3>)
|
||||
PYBIND11_SMART_POINTER_HOLDER_TYPE_CASTERS(TestFactory4, std::shared_ptr<TestFactory4>)
|
||||
PYBIND11_SMART_POINTER_HOLDER_TYPE_CASTERS(TestFactory5, std::shared_ptr<TestFactory5>)
|
||||
PYBIND11_SMART_POINTER_HOLDER_TYPE_CASTERS(TestFactory7, std::shared_ptr<TestFactory7>)
|
||||
|
||||
TEST_SUBMODULE(factory_constructors, m) {
|
||||
|
||||
// Define various trivial types to allow simpler overload resolution:
|
||||
|
@ -112,8 +112,8 @@ UserType TestPropRVP::sv2(1);
|
||||
class NoneTester { public: int answer = 42; };
|
||||
int none1(const NoneTester &obj) { return obj.answer; }
|
||||
int none2(NoneTester *obj) { return obj ? obj->answer : -1; }
|
||||
int none3(std::shared_ptr<NoneTester> &obj) { return obj ? obj->answer : -1; }
|
||||
int none4(std::shared_ptr<NoneTester> *obj) { return obj && *obj ? (*obj)->answer : -1; }
|
||||
int none3(const std::shared_ptr<NoneTester> &obj) { return obj ? obj->answer : -1; }
|
||||
int none4(const std::shared_ptr<NoneTester> *obj) { return obj && *obj ? (*obj)->answer : -1; }
|
||||
int none5(std::shared_ptr<NoneTester> obj) { return obj ? obj->answer : -1; }
|
||||
|
||||
struct StrIssue {
|
||||
@ -148,6 +148,8 @@ struct RefQualified {
|
||||
int constRefQualified(int other) const & { return value + other; }
|
||||
};
|
||||
|
||||
PYBIND11_SMART_POINTER_HOLDER_TYPE_CASTERS(NoneTester, std::shared_ptr<NoneTester>)
|
||||
|
||||
TEST_SUBMODULE(methods_and_attributes, m) {
|
||||
// test_methods_and_attributes
|
||||
py::class_<ExampleMandA> emna(m, "ExampleMandA");
|
||||
|
@ -11,6 +11,8 @@
|
||||
#include "pybind11_tests.h"
|
||||
#include "constructor_stats.h"
|
||||
|
||||
namespace {
|
||||
|
||||
// Many bases for testing that multiple inheritance from many classes (i.e. requiring extra
|
||||
// space for holder constructed flags) works.
|
||||
template <int N> struct BaseN {
|
||||
@ -43,6 +45,38 @@ int WithStatic2::static_value2 = 2;
|
||||
int VanillaStaticMix1::static_value = 12;
|
||||
int VanillaStaticMix2::static_value = 12;
|
||||
|
||||
// test_multiple_inheritance_virtbase
|
||||
struct Base1a {
|
||||
Base1a(int i) : i(i) { }
|
||||
int foo() { return i; }
|
||||
int i;
|
||||
};
|
||||
struct Base2a {
|
||||
Base2a(int i) : i(i) { }
|
||||
int bar() { return i; }
|
||||
int i;
|
||||
};
|
||||
struct Base12a : Base1a, Base2a {
|
||||
Base12a(int i, int j) : Base1a(i), Base2a(j) { }
|
||||
};
|
||||
|
||||
// test_mi_unaligned_base
|
||||
// test_mi_base_return
|
||||
struct I801B1 { int a = 1; I801B1() = default; I801B1(const I801B1 &) = default; virtual ~I801B1() = default; };
|
||||
struct I801B2 { int b = 2; I801B2() = default; I801B2(const I801B2 &) = default; virtual ~I801B2() = default; };
|
||||
struct I801C : I801B1, I801B2 {};
|
||||
struct I801D : I801C {}; // Indirect MI
|
||||
|
||||
} // namespace
|
||||
|
||||
PYBIND11_SMART_POINTER_HOLDER_TYPE_CASTERS(Base1a, std::shared_ptr<Base1a>)
|
||||
PYBIND11_SMART_POINTER_HOLDER_TYPE_CASTERS(Base2a, std::shared_ptr<Base2a>)
|
||||
PYBIND11_SMART_POINTER_HOLDER_TYPE_CASTERS(Base12a, std::shared_ptr<Base12a>)
|
||||
PYBIND11_SMART_POINTER_HOLDER_TYPE_CASTERS(I801B1, std::shared_ptr<I801B1>)
|
||||
PYBIND11_SMART_POINTER_HOLDER_TYPE_CASTERS(I801B2, std::shared_ptr<I801B2>)
|
||||
PYBIND11_SMART_POINTER_HOLDER_TYPE_CASTERS(I801C, std::shared_ptr<I801C>)
|
||||
PYBIND11_SMART_POINTER_HOLDER_TYPE_CASTERS(I801D, std::shared_ptr<I801D>)
|
||||
|
||||
TEST_SUBMODULE(multiple_inheritance, m) {
|
||||
|
||||
// test_multiple_inheritance_mix1
|
||||
@ -99,27 +133,14 @@ TEST_SUBMODULE(multiple_inheritance, m) {
|
||||
// test_multiple_inheritance_virtbase
|
||||
// Test the case where not all base classes are specified, and where pybind11 requires the
|
||||
// py::multiple_inheritance flag to perform proper casting between types.
|
||||
struct Base1a {
|
||||
Base1a(int i) : i(i) { }
|
||||
int foo() { return i; }
|
||||
int i;
|
||||
};
|
||||
py::class_<Base1a, std::shared_ptr<Base1a>>(m, "Base1a")
|
||||
.def(py::init<int>())
|
||||
.def("foo", &Base1a::foo);
|
||||
|
||||
struct Base2a {
|
||||
Base2a(int i) : i(i) { }
|
||||
int bar() { return i; }
|
||||
int i;
|
||||
};
|
||||
py::class_<Base2a, std::shared_ptr<Base2a>>(m, "Base2a")
|
||||
.def(py::init<int>())
|
||||
.def("bar", &Base2a::bar);
|
||||
|
||||
struct Base12a : Base1a, Base2a {
|
||||
Base12a(int i, int j) : Base1a(i), Base2a(j) { }
|
||||
};
|
||||
py::class_<Base12a, /* Base1 missing */ Base2a,
|
||||
std::shared_ptr<Base12a>>(m, "Base12a", py::multiple_inheritance())
|
||||
.def(py::init<int, int>());
|
||||
@ -130,10 +151,6 @@ TEST_SUBMODULE(multiple_inheritance, m) {
|
||||
// test_mi_unaligned_base
|
||||
// test_mi_base_return
|
||||
// Issue #801: invalid casting to derived type with MI bases
|
||||
struct I801B1 { int a = 1; I801B1() = default; I801B1(const I801B1 &) = default; virtual ~I801B1() = default; };
|
||||
struct I801B2 { int b = 2; I801B2() = default; I801B2(const I801B2 &) = default; virtual ~I801B2() = default; };
|
||||
struct I801C : I801B1, I801B2 {};
|
||||
struct I801D : I801C {}; // Indirect MI
|
||||
// Unregistered classes:
|
||||
struct I801B3 { int c = 3; virtual ~I801B3() = default; };
|
||||
struct I801E : I801B3, I801D {};
|
||||
|
@ -15,23 +15,7 @@
|
||||
#include "pybind11_tests.h"
|
||||
#include "object.h"
|
||||
|
||||
// Make pybind aware of the ref-counted wrapper type (s):
|
||||
|
||||
// ref<T> is a wrapper for 'Object' which uses intrusive reference counting
|
||||
// It is always possible to construct a ref<T> from an Object* pointer without
|
||||
// possible inconsistencies, hence the 'true' argument at the end.
|
||||
PYBIND11_DECLARE_HOLDER_TYPE(T, ref<T>, true);
|
||||
// Make pybind11 aware of the non-standard getter member function
|
||||
namespace pybind11 { namespace detail {
|
||||
template <typename T>
|
||||
struct holder_helper<ref<T>> {
|
||||
static const T *get(const ref<T> &p) { return p.get_ptr(); }
|
||||
};
|
||||
} // namespace detail
|
||||
} // namespace pybind11
|
||||
|
||||
// The following is not required anymore for std::shared_ptr, but it should compile without error:
|
||||
PYBIND11_DECLARE_HOLDER_TYPE(T, std::shared_ptr<T>);
|
||||
namespace {
|
||||
|
||||
// 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
|
||||
@ -43,7 +27,6 @@ public:
|
||||
huge_unique_ptr(T *p) : ptr(p) {};
|
||||
T *get() { return ptr.get(); }
|
||||
};
|
||||
PYBIND11_DECLARE_HOLDER_TYPE(T, huge_unique_ptr<T>);
|
||||
|
||||
// Simple custom holder that works like unique_ptr
|
||||
template <typename T>
|
||||
@ -54,7 +37,6 @@ public:
|
||||
T* get() const { return impl.get(); }
|
||||
T* release_ptr() { return impl.release(); }
|
||||
};
|
||||
PYBIND11_DECLARE_HOLDER_TYPE(T, custom_unique_ptr<T>);
|
||||
|
||||
// Simple custom holder that works like shared_ptr and has operator& overload
|
||||
// To obtain address of an instance of this holder pybind should use std::addressof
|
||||
@ -68,7 +50,6 @@ public:
|
||||
T* get() const { return impl.get(); }
|
||||
T** operator&() { throw std::logic_error("Call of overloaded operator& is not expected"); }
|
||||
};
|
||||
PYBIND11_DECLARE_HOLDER_TYPE(T, shared_ptr_with_addressof_operator<T>);
|
||||
|
||||
// Simple custom holder that works like unique_ptr and has operator& overload
|
||||
// To obtain address of an instance of this holder pybind should use std::addressof
|
||||
@ -83,8 +64,245 @@ public:
|
||||
T* release_ptr() { return impl.release(); }
|
||||
T** operator&() { throw std::logic_error("Call of overloaded operator& is not expected"); }
|
||||
};
|
||||
|
||||
// Custom object with builtin reference counting (see 'object.h' for the implementation)
|
||||
class MyObject1 : public Object {
|
||||
public:
|
||||
MyObject1(int value) : value(value) { print_created(this, toString()); }
|
||||
std::string toString() const override { return "MyObject1[" + std::to_string(value) + "]"; }
|
||||
protected:
|
||||
~MyObject1() override { print_destroyed(this); }
|
||||
private:
|
||||
int value;
|
||||
};
|
||||
|
||||
// Object managed by a std::shared_ptr<>
|
||||
class MyObject2 {
|
||||
public:
|
||||
MyObject2(const MyObject2 &) = default;
|
||||
MyObject2(int value) : value(value) { print_created(this, toString()); }
|
||||
std::string toString() const { return "MyObject2[" + std::to_string(value) + "]"; }
|
||||
virtual ~MyObject2() { print_destroyed(this); }
|
||||
private:
|
||||
int value;
|
||||
};
|
||||
|
||||
// Object managed by a std::shared_ptr<>, additionally derives from std::enable_shared_from_this<>
|
||||
class MyObject3 : public std::enable_shared_from_this<MyObject3> {
|
||||
public:
|
||||
MyObject3(const MyObject3 &) = default;
|
||||
MyObject3(int value) : value(value) { print_created(this, toString()); }
|
||||
std::string toString() const { return "MyObject3[" + std::to_string(value) + "]"; }
|
||||
virtual ~MyObject3() { print_destroyed(this); }
|
||||
private:
|
||||
int value;
|
||||
};
|
||||
|
||||
// test_unique_nodelete
|
||||
// Object with a private destructor
|
||||
class MyObject4;
|
||||
static std::unordered_set<MyObject4 *> myobject4_instances;
|
||||
class MyObject4 {
|
||||
public:
|
||||
MyObject4(int value) : value{value} {
|
||||
print_created(this);
|
||||
myobject4_instances.insert(this);
|
||||
}
|
||||
int value;
|
||||
|
||||
static void cleanupAllInstances() {
|
||||
auto tmp = std::move(myobject4_instances);
|
||||
myobject4_instances.clear();
|
||||
for (auto o : tmp)
|
||||
delete o;
|
||||
}
|
||||
private:
|
||||
~MyObject4() {
|
||||
myobject4_instances.erase(this);
|
||||
print_destroyed(this);
|
||||
}
|
||||
};
|
||||
|
||||
// test_unique_deleter
|
||||
// Object with std::unique_ptr<T, D> where D is not matching the base class
|
||||
// Object with a protected destructor
|
||||
class MyObject4a;
|
||||
static std::unordered_set<MyObject4a *> myobject4a_instances;
|
||||
class MyObject4a {
|
||||
public:
|
||||
MyObject4a(int i) {
|
||||
value = i;
|
||||
print_created(this);
|
||||
myobject4a_instances.insert(this);
|
||||
};
|
||||
int value;
|
||||
|
||||
static void cleanupAllInstances() {
|
||||
auto tmp = std::move(myobject4a_instances);
|
||||
myobject4a_instances.clear();
|
||||
for (auto o : tmp)
|
||||
delete o;
|
||||
}
|
||||
protected:
|
||||
virtual ~MyObject4a() {
|
||||
myobject4a_instances.erase(this);
|
||||
print_destroyed(this);
|
||||
}
|
||||
};
|
||||
|
||||
// Object derived but with public destructor and no Deleter in default holder
|
||||
class MyObject4b : public MyObject4a {
|
||||
public:
|
||||
MyObject4b(int i) : MyObject4a(i) { print_created(this); }
|
||||
~MyObject4b() override { print_destroyed(this); }
|
||||
};
|
||||
|
||||
// test_large_holder
|
||||
class MyObject5 { // managed by huge_unique_ptr
|
||||
public:
|
||||
MyObject5(int value) : value{value} { print_created(this); }
|
||||
~MyObject5() { print_destroyed(this); }
|
||||
int value;
|
||||
};
|
||||
|
||||
// test_shared_ptr_and_references
|
||||
struct SharedPtrRef {
|
||||
struct A {
|
||||
A() { print_created(this); }
|
||||
A(const A &) { print_copy_created(this); }
|
||||
A(A &&) { print_move_created(this); }
|
||||
~A() { print_destroyed(this); }
|
||||
};
|
||||
|
||||
A value = {};
|
||||
std::shared_ptr<A> shared = std::make_shared<A>();
|
||||
};
|
||||
|
||||
// test_shared_ptr_from_this_and_references
|
||||
struct SharedFromThisRef {
|
||||
struct B : std::enable_shared_from_this<B> {
|
||||
B() { print_created(this); }
|
||||
B(const B &) : std::enable_shared_from_this<B>() { print_copy_created(this); }
|
||||
B(B &&) : std::enable_shared_from_this<B>() { print_move_created(this); }
|
||||
~B() { print_destroyed(this); }
|
||||
};
|
||||
|
||||
B value = {};
|
||||
std::shared_ptr<B> shared = std::make_shared<B>();
|
||||
};
|
||||
|
||||
// Issue #865: shared_from_this doesn't work with virtual inheritance
|
||||
struct SharedFromThisVBase : std::enable_shared_from_this<SharedFromThisVBase> {
|
||||
SharedFromThisVBase() = default;
|
||||
SharedFromThisVBase(const SharedFromThisVBase &) = default;
|
||||
virtual ~SharedFromThisVBase() = default;
|
||||
};
|
||||
struct SharedFromThisVirt : virtual SharedFromThisVBase {};
|
||||
|
||||
// test_move_only_holder
|
||||
struct C {
|
||||
C() { print_created(this); }
|
||||
~C() { print_destroyed(this); }
|
||||
};
|
||||
|
||||
// test_holder_with_addressof_operator
|
||||
struct TypeForHolderWithAddressOf {
|
||||
TypeForHolderWithAddressOf() { print_created(this); }
|
||||
TypeForHolderWithAddressOf(const TypeForHolderWithAddressOf &) { print_copy_created(this); }
|
||||
TypeForHolderWithAddressOf(TypeForHolderWithAddressOf &&) { print_move_created(this); }
|
||||
~TypeForHolderWithAddressOf() { print_destroyed(this); }
|
||||
std::string toString() const {
|
||||
return "TypeForHolderWithAddressOf[" + std::to_string(value) + "]";
|
||||
}
|
||||
int value = 42;
|
||||
};
|
||||
|
||||
// test_move_only_holder_with_addressof_operator
|
||||
struct TypeForMoveOnlyHolderWithAddressOf {
|
||||
TypeForMoveOnlyHolderWithAddressOf(int value) : value{value} { print_created(this); }
|
||||
~TypeForMoveOnlyHolderWithAddressOf() { print_destroyed(this); }
|
||||
std::string toString() const {
|
||||
return "MoveOnlyHolderWithAddressOf[" + std::to_string(value) + "]";
|
||||
}
|
||||
int value;
|
||||
};
|
||||
|
||||
// test_smart_ptr_from_default
|
||||
struct HeldByDefaultHolder { };
|
||||
|
||||
// test_shared_ptr_gc
|
||||
// #187: issue involving std::shared_ptr<> return value policy & garbage collection
|
||||
struct ElementBase {
|
||||
virtual ~ElementBase() = default; /* Force creation of virtual table */
|
||||
ElementBase() = default;
|
||||
ElementBase(const ElementBase&) = delete;
|
||||
};
|
||||
|
||||
struct ElementA : ElementBase {
|
||||
ElementA(int v) : v(v) { }
|
||||
int value() { return v; }
|
||||
int v;
|
||||
};
|
||||
|
||||
struct ElementList {
|
||||
void add(std::shared_ptr<ElementBase> e) { l.push_back(e); }
|
||||
std::vector<std::shared_ptr<ElementBase>> l;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
// ref<T> is a wrapper for 'Object' which uses intrusive reference counting
|
||||
// It is always possible to construct a ref<T> from an Object* pointer without
|
||||
// possible inconsistencies, hence the 'true' argument at the end.
|
||||
// Make pybind11 aware of the non-standard getter member function
|
||||
namespace pybind11 { namespace detail {
|
||||
template <typename T>
|
||||
struct holder_helper<ref<T>> {
|
||||
static const T *get(const ref<T> &p) { return p.get_ptr(); }
|
||||
};
|
||||
} // namespace detail
|
||||
} // namespace pybind11
|
||||
|
||||
// Make pybind aware of the ref-counted wrapper type (s):
|
||||
PYBIND11_DECLARE_HOLDER_TYPE(T, ref<T>, true);
|
||||
// The following is not required anymore for std::shared_ptr, but it should compile without error:
|
||||
PYBIND11_DECLARE_HOLDER_TYPE(T, std::shared_ptr<T>);
|
||||
PYBIND11_DECLARE_HOLDER_TYPE(T, huge_unique_ptr<T>);
|
||||
PYBIND11_DECLARE_HOLDER_TYPE(T, custom_unique_ptr<T>);
|
||||
PYBIND11_DECLARE_HOLDER_TYPE(T, shared_ptr_with_addressof_operator<T>);
|
||||
PYBIND11_DECLARE_HOLDER_TYPE(T, unique_ptr_with_addressof_operator<T>);
|
||||
|
||||
PYBIND11_SMART_POINTER_HOLDER_TYPE_CASTERS(Object, ref<Object>)
|
||||
PYBIND11_SMART_POINTER_HOLDER_TYPE_CASTERS(MyObject1, ref<MyObject1>)
|
||||
PYBIND11_SMART_POINTER_HOLDER_TYPE_CASTERS(MyObject2, std::shared_ptr<MyObject2>)
|
||||
PYBIND11_SMART_POINTER_HOLDER_TYPE_CASTERS(MyObject3, std::shared_ptr<MyObject3>)
|
||||
PYBIND11_SMART_POINTER_HOLDER_TYPE_CASTERS(MyObject4, std::unique_ptr<MyObject4, py::nodelete>)
|
||||
PYBIND11_SMART_POINTER_HOLDER_TYPE_CASTERS(MyObject4a, std::unique_ptr<MyObject4a, py::nodelete>)
|
||||
PYBIND11_SMART_POINTER_HOLDER_TYPE_CASTERS(MyObject4b, std::unique_ptr<MyObject4b>)
|
||||
PYBIND11_SMART_POINTER_HOLDER_TYPE_CASTERS(MyObject5, huge_unique_ptr<MyObject5>)
|
||||
PYBIND11_SMART_POINTER_HOLDER_TYPE_CASTERS(SharedPtrRef::A, std::shared_ptr<SharedPtrRef::A>)
|
||||
PYBIND11_SMART_POINTER_HOLDER_TYPE_CASTERS(SharedPtrRef, std::unique_ptr<SharedPtrRef>)
|
||||
PYBIND11_SMART_POINTER_HOLDER_TYPE_CASTERS(SharedFromThisRef::B, std::shared_ptr<SharedFromThisRef::B>)
|
||||
PYBIND11_SMART_POINTER_HOLDER_TYPE_CASTERS(SharedFromThisRef, std::unique_ptr<SharedFromThisRef>)
|
||||
PYBIND11_SMART_POINTER_HOLDER_TYPE_CASTERS(SharedFromThisVirt, std::shared_ptr<SharedFromThisVirt>)
|
||||
PYBIND11_SMART_POINTER_HOLDER_TYPE_CASTERS(C, custom_unique_ptr<C>)
|
||||
PYBIND11_SMART_POINTER_HOLDER_TYPE_CASTERS(TypeForHolderWithAddressOf, shared_ptr_with_addressof_operator<TypeForHolderWithAddressOf>)
|
||||
PYBIND11_SMART_POINTER_HOLDER_TYPE_CASTERS(TypeForMoveOnlyHolderWithAddressOf, unique_ptr_with_addressof_operator<TypeForMoveOnlyHolderWithAddressOf>)
|
||||
PYBIND11_SMART_POINTER_HOLDER_TYPE_CASTERS(HeldByDefaultHolder, std::unique_ptr<HeldByDefaultHolder>)
|
||||
PYBIND11_SMART_POINTER_HOLDER_TYPE_CASTERS(ElementBase, std::shared_ptr<ElementBase>)
|
||||
PYBIND11_SMART_POINTER_HOLDER_TYPE_CASTERS(ElementA, std::shared_ptr<ElementA>)
|
||||
PYBIND11_SMART_POINTER_HOLDER_TYPE_CASTERS(ElementList, std::shared_ptr<ElementList>)
|
||||
|
||||
#ifdef PYBIND11_USE_SMART_HOLDER_AS_DEFAULT
|
||||
// To prevent triggering a static_assert in the smart_holder code.
|
||||
// This is a very special case, because the associated test exercises a holder mismatch.
|
||||
namespace pybind11 { namespace detail {
|
||||
template <>
|
||||
class type_caster<std::shared_ptr<HeldByDefaultHolder>>
|
||||
: public copyable_holder_caster<HeldByDefaultHolder, std::shared_ptr<HeldByDefaultHolder>> {};
|
||||
} // namespace detail
|
||||
} // namespace pybind11
|
||||
#endif
|
||||
|
||||
TEST_SUBMODULE(smart_ptr, m) {
|
||||
|
||||
@ -94,16 +312,6 @@ TEST_SUBMODULE(smart_ptr, m) {
|
||||
py::class_<Object, ref<Object>> obj(m, "Object");
|
||||
obj.def("getRefCount", &Object::getRefCount);
|
||||
|
||||
// Custom object with builtin reference counting (see 'object.h' for the implementation)
|
||||
class MyObject1 : public Object {
|
||||
public:
|
||||
MyObject1(int value) : value(value) { print_created(this, toString()); }
|
||||
std::string toString() const override { return "MyObject1[" + std::to_string(value) + "]"; }
|
||||
protected:
|
||||
~MyObject1() override { print_destroyed(this); }
|
||||
private:
|
||||
int value;
|
||||
};
|
||||
py::class_<MyObject1, ref<MyObject1>>(m, "MyObject1", obj)
|
||||
.def(py::init<int>());
|
||||
py::implicitly_convertible<py::int_, MyObject1>();
|
||||
@ -124,17 +332,6 @@ TEST_SUBMODULE(smart_ptr, m) {
|
||||
// Expose constructor stats for the ref type
|
||||
m.def("cstats_ref", &ConstructorStats::get<ref_tag>);
|
||||
|
||||
|
||||
// Object managed by a std::shared_ptr<>
|
||||
class MyObject2 {
|
||||
public:
|
||||
MyObject2(const MyObject2 &) = default;
|
||||
MyObject2(int value) : value(value) { print_created(this, toString()); }
|
||||
std::string toString() const { return "MyObject2[" + std::to_string(value) + "]"; }
|
||||
virtual ~MyObject2() { print_destroyed(this); }
|
||||
private:
|
||||
int value;
|
||||
};
|
||||
py::class_<MyObject2, std::shared_ptr<MyObject2>>(m, "MyObject2")
|
||||
.def(py::init<int>());
|
||||
m.def("make_myobject2_1", []() { return new MyObject2(6); });
|
||||
@ -144,16 +341,6 @@ TEST_SUBMODULE(smart_ptr, m) {
|
||||
m.def("print_myobject2_3", [](const std::shared_ptr<MyObject2> &obj) { py::print(obj->toString()); });
|
||||
m.def("print_myobject2_4", [](const std::shared_ptr<MyObject2> *obj) { py::print((*obj)->toString()); });
|
||||
|
||||
// Object managed by a std::shared_ptr<>, additionally derives from std::enable_shared_from_this<>
|
||||
class MyObject3 : public std::enable_shared_from_this<MyObject3> {
|
||||
public:
|
||||
MyObject3(const MyObject3 &) = default;
|
||||
MyObject3(int value) : value(value) { print_created(this, toString()); }
|
||||
std::string toString() const { return "MyObject3[" + std::to_string(value) + "]"; }
|
||||
virtual ~MyObject3() { print_destroyed(this); }
|
||||
private:
|
||||
int value;
|
||||
};
|
||||
py::class_<MyObject3, std::shared_ptr<MyObject3>>(m, "MyObject3")
|
||||
.def(py::init<int>());
|
||||
m.def("make_myobject3_1", []() { return new MyObject3(8); });
|
||||
@ -174,101 +361,26 @@ TEST_SUBMODULE(smart_ptr, m) {
|
||||
return good;
|
||||
});
|
||||
|
||||
// test_unique_nodelete
|
||||
// Object with a private destructor
|
||||
class MyObject4;
|
||||
static std::unordered_set<MyObject4 *> myobject4_instances;
|
||||
class MyObject4 {
|
||||
public:
|
||||
MyObject4(int value) : value{value} {
|
||||
print_created(this);
|
||||
myobject4_instances.insert(this);
|
||||
}
|
||||
int value;
|
||||
|
||||
static void cleanupAllInstances() {
|
||||
auto tmp = std::move(myobject4_instances);
|
||||
myobject4_instances.clear();
|
||||
for (auto o : tmp)
|
||||
delete o;
|
||||
}
|
||||
private:
|
||||
~MyObject4() {
|
||||
myobject4_instances.erase(this);
|
||||
print_destroyed(this);
|
||||
}
|
||||
};
|
||||
py::class_<MyObject4, std::unique_ptr<MyObject4, py::nodelete>>(m, "MyObject4")
|
||||
.def(py::init<int>())
|
||||
.def_readwrite("value", &MyObject4::value)
|
||||
.def_static("cleanup_all_instances", &MyObject4::cleanupAllInstances);
|
||||
|
||||
// test_unique_deleter
|
||||
// Object with std::unique_ptr<T, D> where D is not matching the base class
|
||||
// Object with a protected destructor
|
||||
class MyObject4a;
|
||||
static std::unordered_set<MyObject4a *> myobject4a_instances;
|
||||
class MyObject4a {
|
||||
public:
|
||||
MyObject4a(int i) {
|
||||
value = i;
|
||||
print_created(this);
|
||||
myobject4a_instances.insert(this);
|
||||
};
|
||||
int value;
|
||||
|
||||
static void cleanupAllInstances() {
|
||||
auto tmp = std::move(myobject4a_instances);
|
||||
myobject4a_instances.clear();
|
||||
for (auto o : tmp)
|
||||
delete o;
|
||||
}
|
||||
protected:
|
||||
virtual ~MyObject4a() {
|
||||
myobject4a_instances.erase(this);
|
||||
print_destroyed(this);
|
||||
}
|
||||
};
|
||||
py::class_<MyObject4a, std::unique_ptr<MyObject4a, py::nodelete>>(m, "MyObject4a")
|
||||
.def(py::init<int>())
|
||||
.def_readwrite("value", &MyObject4a::value)
|
||||
.def_static("cleanup_all_instances", &MyObject4a::cleanupAllInstances);
|
||||
|
||||
// Object derived but with public destructor and no Deleter in default holder
|
||||
class MyObject4b : public MyObject4a {
|
||||
public:
|
||||
MyObject4b(int i) : MyObject4a(i) { print_created(this); }
|
||||
~MyObject4b() override { print_destroyed(this); }
|
||||
};
|
||||
py::class_<MyObject4b, MyObject4a>(m, "MyObject4b")
|
||||
py::class_<MyObject4b, MyObject4a, std::unique_ptr<MyObject4b>>(m, "MyObject4b")
|
||||
.def(py::init<int>());
|
||||
|
||||
// test_large_holder
|
||||
class MyObject5 { // managed by huge_unique_ptr
|
||||
public:
|
||||
MyObject5(int value) : value{value} { print_created(this); }
|
||||
~MyObject5() { print_destroyed(this); }
|
||||
int value;
|
||||
};
|
||||
py::class_<MyObject5, huge_unique_ptr<MyObject5>>(m, "MyObject5")
|
||||
.def(py::init<int>())
|
||||
.def_readwrite("value", &MyObject5::value);
|
||||
|
||||
// test_shared_ptr_and_references
|
||||
struct SharedPtrRef {
|
||||
struct A {
|
||||
A() { print_created(this); }
|
||||
A(const A &) { print_copy_created(this); }
|
||||
A(A &&) { print_move_created(this); }
|
||||
~A() { print_destroyed(this); }
|
||||
};
|
||||
|
||||
A value = {};
|
||||
std::shared_ptr<A> shared = std::make_shared<A>();
|
||||
};
|
||||
using A = SharedPtrRef::A;
|
||||
py::class_<A, std::shared_ptr<A>>(m, "A");
|
||||
py::class_<SharedPtrRef>(m, "SharedPtrRef")
|
||||
py::class_<SharedPtrRef, std::unique_ptr<SharedPtrRef>>(m, "SharedPtrRef")
|
||||
.def(py::init<>())
|
||||
.def_readonly("ref", &SharedPtrRef::value)
|
||||
.def_property_readonly("copy", [](const SharedPtrRef &s) { return s.value; },
|
||||
@ -279,21 +391,9 @@ TEST_SUBMODULE(smart_ptr, m) {
|
||||
.def("set_ref", [](SharedPtrRef &, const A &) { return true; })
|
||||
.def("set_holder", [](SharedPtrRef &, std::shared_ptr<A>) { return true; });
|
||||
|
||||
// test_shared_ptr_from_this_and_references
|
||||
struct SharedFromThisRef {
|
||||
struct B : std::enable_shared_from_this<B> {
|
||||
B() { print_created(this); }
|
||||
B(const B &) : std::enable_shared_from_this<B>() { print_copy_created(this); }
|
||||
B(B &&) : std::enable_shared_from_this<B>() { print_move_created(this); }
|
||||
~B() { print_destroyed(this); }
|
||||
};
|
||||
|
||||
B value = {};
|
||||
std::shared_ptr<B> shared = std::make_shared<B>();
|
||||
};
|
||||
using B = SharedFromThisRef::B;
|
||||
py::class_<B, std::shared_ptr<B>>(m, "B");
|
||||
py::class_<SharedFromThisRef>(m, "SharedFromThisRef")
|
||||
py::class_<SharedFromThisRef, std::unique_ptr<SharedFromThisRef>>(m, "SharedFromThisRef")
|
||||
.def(py::init<>())
|
||||
.def_readonly("bad_wp", &SharedFromThisRef::value)
|
||||
.def_property_readonly("ref", [](const SharedFromThisRef &s) -> const B & { return *s.shared; })
|
||||
@ -305,37 +405,14 @@ TEST_SUBMODULE(smart_ptr, m) {
|
||||
.def("set_ref", [](SharedFromThisRef &, const B &) { return true; })
|
||||
.def("set_holder", [](SharedFromThisRef &, std::shared_ptr<B>) { return true; });
|
||||
|
||||
// Issue #865: shared_from_this doesn't work with virtual inheritance
|
||||
struct SharedFromThisVBase : std::enable_shared_from_this<SharedFromThisVBase> {
|
||||
SharedFromThisVBase() = default;
|
||||
SharedFromThisVBase(const SharedFromThisVBase &) = default;
|
||||
virtual ~SharedFromThisVBase() = default;
|
||||
};
|
||||
struct SharedFromThisVirt : virtual SharedFromThisVBase {};
|
||||
static std::shared_ptr<SharedFromThisVirt> sft(new SharedFromThisVirt());
|
||||
py::class_<SharedFromThisVirt, std::shared_ptr<SharedFromThisVirt>>(m, "SharedFromThisVirt")
|
||||
.def_static("get", []() { return sft.get(); });
|
||||
|
||||
// test_move_only_holder
|
||||
struct C {
|
||||
C() { print_created(this); }
|
||||
~C() { print_destroyed(this); }
|
||||
};
|
||||
py::class_<C, custom_unique_ptr<C>>(m, "TypeWithMoveOnlyHolder")
|
||||
.def_static("make", []() { return custom_unique_ptr<C>(new C); })
|
||||
.def_static("make_as_object", []() { return py::cast(custom_unique_ptr<C>(new C)); });
|
||||
|
||||
// test_holder_with_addressof_operator
|
||||
struct TypeForHolderWithAddressOf {
|
||||
TypeForHolderWithAddressOf() { print_created(this); }
|
||||
TypeForHolderWithAddressOf(const TypeForHolderWithAddressOf &) { print_copy_created(this); }
|
||||
TypeForHolderWithAddressOf(TypeForHolderWithAddressOf &&) { print_move_created(this); }
|
||||
~TypeForHolderWithAddressOf() { print_destroyed(this); }
|
||||
std::string toString() const {
|
||||
return "TypeForHolderWithAddressOf[" + std::to_string(value) + "]";
|
||||
}
|
||||
int value = 42;
|
||||
};
|
||||
using HolderWithAddressOf = shared_ptr_with_addressof_operator<TypeForHolderWithAddressOf>;
|
||||
py::class_<TypeForHolderWithAddressOf, HolderWithAddressOf>(m, "TypeForHolderWithAddressOf")
|
||||
.def_static("make", []() { return HolderWithAddressOf(new TypeForHolderWithAddressOf); })
|
||||
@ -345,49 +422,22 @@ TEST_SUBMODULE(smart_ptr, m) {
|
||||
.def("print_object_3", [](const HolderWithAddressOf &obj) { py::print(obj.get()->toString()); })
|
||||
.def("print_object_4", [](const HolderWithAddressOf *obj) { py::print((*obj).get()->toString()); });
|
||||
|
||||
// test_move_only_holder_with_addressof_operator
|
||||
struct TypeForMoveOnlyHolderWithAddressOf {
|
||||
TypeForMoveOnlyHolderWithAddressOf(int value) : value{value} { print_created(this); }
|
||||
~TypeForMoveOnlyHolderWithAddressOf() { print_destroyed(this); }
|
||||
std::string toString() const {
|
||||
return "MoveOnlyHolderWithAddressOf[" + std::to_string(value) + "]";
|
||||
}
|
||||
int value;
|
||||
};
|
||||
using MoveOnlyHolderWithAddressOf = unique_ptr_with_addressof_operator<TypeForMoveOnlyHolderWithAddressOf>;
|
||||
py::class_<TypeForMoveOnlyHolderWithAddressOf, MoveOnlyHolderWithAddressOf>(m, "TypeForMoveOnlyHolderWithAddressOf")
|
||||
.def_static("make", []() { return MoveOnlyHolderWithAddressOf(new TypeForMoveOnlyHolderWithAddressOf(0)); })
|
||||
.def_readwrite("value", &TypeForMoveOnlyHolderWithAddressOf::value)
|
||||
.def("print_object", [](const TypeForMoveOnlyHolderWithAddressOf *obj) { py::print(obj->toString()); });
|
||||
|
||||
// test_smart_ptr_from_default
|
||||
struct HeldByDefaultHolder { };
|
||||
py::class_<HeldByDefaultHolder>(m, "HeldByDefaultHolder")
|
||||
py::class_<HeldByDefaultHolder, std::unique_ptr<HeldByDefaultHolder>>(m, "HeldByDefaultHolder")
|
||||
.def(py::init<>())
|
||||
.def_static("load_shared_ptr", [](std::shared_ptr<HeldByDefaultHolder>) {});
|
||||
|
||||
// test_shared_ptr_gc
|
||||
// #187: issue involving std::shared_ptr<> return value policy & garbage collection
|
||||
struct ElementBase {
|
||||
virtual ~ElementBase() = default; /* Force creation of virtual table */
|
||||
ElementBase() = default;
|
||||
ElementBase(const ElementBase&) = delete;
|
||||
};
|
||||
py::class_<ElementBase, std::shared_ptr<ElementBase>>(m, "ElementBase");
|
||||
|
||||
struct ElementA : ElementBase {
|
||||
ElementA(int v) : v(v) { }
|
||||
int value() { return v; }
|
||||
int v;
|
||||
};
|
||||
py::class_<ElementA, ElementBase, std::shared_ptr<ElementA>>(m, "ElementA")
|
||||
.def(py::init<int>())
|
||||
.def("value", &ElementA::value);
|
||||
|
||||
struct ElementList {
|
||||
void add(std::shared_ptr<ElementBase> e) { l.push_back(e); }
|
||||
std::vector<std::shared_ptr<ElementBase>> l;
|
||||
};
|
||||
py::class_<ElementList, std::shared_ptr<ElementList>>(m, "ElementList")
|
||||
.def(py::init<>())
|
||||
.def("add", &ElementList::add)
|
||||
|
@ -302,9 +302,9 @@ def test_smart_ptr_from_default():
|
||||
instance = m.HeldByDefaultHolder()
|
||||
with pytest.raises(RuntimeError) as excinfo:
|
||||
m.HeldByDefaultHolder.load_shared_ptr(instance)
|
||||
assert (
|
||||
"Unable to load a custom holder type from a "
|
||||
"default-holder instance" in str(excinfo.value)
|
||||
assert str(excinfo.value) in (
|
||||
"Unable to load a smart-pointer type from a non-smart_holder instance.",
|
||||
"Unable to load a custom holder type from a default-holder instance",
|
||||
)
|
||||
|
||||
|
||||
|
54
ubench/holder_comparison.cpp
Normal file
54
ubench/holder_comparison.cpp
Normal file
@ -0,0 +1,54 @@
|
||||
#include <pybind11/smart_holder.h>
|
||||
|
||||
#include "number_bucket.h"
|
||||
|
||||
#include <cstddef>
|
||||
#include <memory>
|
||||
|
||||
namespace hc { // holder comparison
|
||||
|
||||
using nb_up = pybind11_ubench::number_bucket<1>;
|
||||
using nb_sp = pybind11_ubench::number_bucket<2>;
|
||||
using nb_pu = pybind11_ubench::number_bucket<3>;
|
||||
using nb_sh = pybind11_ubench::number_bucket<4>;
|
||||
|
||||
namespace py = pybind11;
|
||||
|
||||
template <typename WrappedType, typename HolderType>
|
||||
void wrap_number_bucket(py::module m, const char *class_name) {
|
||||
py::class_<WrappedType, HolderType>(m, class_name)
|
||||
.def(py::init<std::size_t>(), py::arg("data_size") = 0)
|
||||
.def("sum", &WrappedType::sum)
|
||||
.def("add", &WrappedType::add, py::arg("other"));
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
class padded_unique_ptr {
|
||||
std::unique_ptr<T> ptr;
|
||||
char padding[sizeof(py::smart_holder) - sizeof(std::unique_ptr<T>)];
|
||||
|
||||
public:
|
||||
padded_unique_ptr(T *p) : ptr(p) {}
|
||||
T *get() { return ptr.get(); }
|
||||
};
|
||||
|
||||
static_assert(sizeof(padded_unique_ptr<nb_pu>) == sizeof(py::smart_holder),
|
||||
"Unexpected sizeof mismatch.");
|
||||
|
||||
} // namespace hc
|
||||
|
||||
PYBIND11_DECLARE_HOLDER_TYPE(T, hc::padded_unique_ptr<T>);
|
||||
|
||||
PYBIND11_SMART_POINTER_HOLDER_TYPE_CASTERS(hc::nb_up, std::unique_ptr<hc::nb_up>)
|
||||
PYBIND11_SMART_POINTER_HOLDER_TYPE_CASTERS(hc::nb_sp, std::shared_ptr<hc::nb_sp>)
|
||||
PYBIND11_SMART_POINTER_HOLDER_TYPE_CASTERS(hc::nb_pu, hc::padded_unique_ptr<hc::nb_pu>)
|
||||
PYBIND11_SMART_HOLDER_TYPE_CASTERS(hc::nb_sh)
|
||||
|
||||
PYBIND11_MODULE(pybind11_ubench_holder_comparison, m) {
|
||||
using namespace hc;
|
||||
m.def("sizeof_smart_holder", []() { return sizeof(py::smart_holder); });
|
||||
wrap_number_bucket<nb_up, std::unique_ptr<nb_up>>(m, "number_bucket_up");
|
||||
wrap_number_bucket<nb_sp, std::shared_ptr<nb_sp>>(m, "number_bucket_sp");
|
||||
wrap_number_bucket<nb_pu, padded_unique_ptr<nb_pu>>(m, "number_bucket_pu");
|
||||
wrap_number_bucket<nb_sh, py::smart_holder>(m, "number_bucket_sh");
|
||||
}
|
140
ubench/holder_comparison.py
Normal file
140
ubench/holder_comparison.py
Normal file
@ -0,0 +1,140 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""Simple comparison of holder performances, relative to unique_ptr holder."""
|
||||
|
||||
from __future__ import absolute_import
|
||||
from __future__ import division
|
||||
from __future__ import print_function
|
||||
|
||||
import pybind11_ubench_holder_comparison as m
|
||||
|
||||
import collections
|
||||
import sys
|
||||
import time
|
||||
|
||||
number_bucket_pc = None
|
||||
|
||||
|
||||
def pflush(*args, **kwargs):
|
||||
result = print(*args, **kwargs)
|
||||
# Using "file" here because it is the name of the built-in keyword argument.
|
||||
file = kwargs.get("file", sys.stdout) # pylint: disable=redefined-builtin
|
||||
file.flush() # file object must have a flush method.
|
||||
return result
|
||||
|
||||
|
||||
def run(args):
|
||||
if not args:
|
||||
size_exponent_min = 0
|
||||
size_exponent_max = 16
|
||||
size_exponent_step = 4
|
||||
call_repetitions_first_pass = 100
|
||||
call_repetitions_target_elapsed_secs = 0.1
|
||||
num_samples = 10
|
||||
selected_holder_type = "all"
|
||||
else:
|
||||
assert len(args) == 7, (
|
||||
"size_exponent_min size_exponent_max size_exponent_step"
|
||||
" call_repetitions_first_pass call_repetitions_target_elapsed_secs"
|
||||
" num_samples selected_holder_type"
|
||||
)
|
||||
size_exponent_min = int(args[0])
|
||||
size_exponent_max = int(args[1])
|
||||
size_exponent_step = int(args[2])
|
||||
call_repetitions_first_pass = int(args[3])
|
||||
call_repetitions_target_elapsed_secs = float(args[4])
|
||||
num_samples = int(args[5])
|
||||
selected_holder_type = args[6]
|
||||
pflush(
|
||||
"command-line arguments:",
|
||||
size_exponent_min,
|
||||
size_exponent_max,
|
||||
size_exponent_step,
|
||||
call_repetitions_first_pass,
|
||||
"%.3f" % call_repetitions_target_elapsed_secs,
|
||||
num_samples,
|
||||
selected_holder_type,
|
||||
)
|
||||
pflush("sizeof_smart_holder:", m.sizeof_smart_holder())
|
||||
|
||||
def find_call_repetitions(
|
||||
callable,
|
||||
time_delta_floor=1.0e-6,
|
||||
target_elapsed_secs_multiplier=1.05, # Empirical.
|
||||
target_elapsed_secs_tolerance=0.05,
|
||||
max_iterations=100,
|
||||
):
|
||||
td_target = (
|
||||
call_repetitions_target_elapsed_secs * target_elapsed_secs_multiplier
|
||||
)
|
||||
crd = call_repetitions_first_pass
|
||||
for _ in range(max_iterations):
|
||||
td = callable(crd)
|
||||
crd = max(1, int(td_target * crd / max(td, time_delta_floor)))
|
||||
if abs(td - td_target) / td_target < target_elapsed_secs_tolerance:
|
||||
return crd
|
||||
raise RuntimeError("find_call_repetitions failure: max_iterations exceeded.")
|
||||
|
||||
for size_exponent in range(
|
||||
size_exponent_min, size_exponent_max + 1, size_exponent_step
|
||||
):
|
||||
data_size = 2 ** size_exponent
|
||||
pflush(data_size, "data_size")
|
||||
ratios = collections.defaultdict(list)
|
||||
call_repetitions = None
|
||||
for _ in range(num_samples):
|
||||
row_0 = None
|
||||
for nb_label, nb_type in [
|
||||
("up", m.number_bucket_up),
|
||||
("sp", m.number_bucket_sp),
|
||||
("pu", m.number_bucket_pu),
|
||||
("sh", m.number_bucket_sh),
|
||||
("pc", number_bucket_pc),
|
||||
]:
|
||||
if nb_label == "pc" and nb_type is None:
|
||||
continue
|
||||
if selected_holder_type != "all" and nb_label != selected_holder_type:
|
||||
continue
|
||||
nb1 = nb_type(data_size)
|
||||
nb2 = nb_type(data_size)
|
||||
|
||||
def many_sum(call_repetitions):
|
||||
assert int(round(nb1.sum())) == data_size
|
||||
t0 = time.time()
|
||||
for _ in range(call_repetitions):
|
||||
nb1.sum()
|
||||
return time.time() - t0
|
||||
|
||||
def many_add(call_repetitions):
|
||||
assert nb1.add(nb2) == data_size
|
||||
t0 = time.time()
|
||||
for _ in range(call_repetitions):
|
||||
nb1.add(nb2)
|
||||
return time.time() - t0
|
||||
|
||||
if call_repetitions is None:
|
||||
call_repetitions = find_call_repetitions(many_sum)
|
||||
pflush(call_repetitions, "call_repetitions")
|
||||
|
||||
td_sum = many_sum(call_repetitions)
|
||||
td_add = many_add(call_repetitions)
|
||||
row = [td_sum, td_add]
|
||||
if row_0 is None:
|
||||
pflush(" Sum Add ratS ratA")
|
||||
row_0 = row
|
||||
else:
|
||||
for curr, prev in zip(row, row_0):
|
||||
if prev:
|
||||
rat = curr / prev
|
||||
else:
|
||||
rat = -1
|
||||
row.append(curr / prev)
|
||||
ratios[nb_label + "_ratS"].append(row[-2])
|
||||
ratios[nb_label + "_ratA"].append(row[-1])
|
||||
pflush(nb_label, " ".join(["%.3f" % v for v in row]))
|
||||
pflush(" Min Mean Max")
|
||||
for key, rat in ratios.items():
|
||||
print(key, "%5.3f %5.3f %5.3f" % (min(rat), sum(rat) / len(rat), max(rat)))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
run(args=sys.argv[1:])
|
66
ubench/holder_comparison_extract_sheet_data.py
Normal file
66
ubench/holder_comparison_extract_sheet_data.py
Normal file
@ -0,0 +1,66 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""Extract mean ratios from holder_comparison.py output."""
|
||||
|
||||
from __future__ import absolute_import
|
||||
from __future__ import division
|
||||
from __future__ import print_function
|
||||
|
||||
import sys
|
||||
|
||||
|
||||
def run(args):
|
||||
assert len(args) == 1, "log_holder_comparison.txt"
|
||||
|
||||
log_lines = open(args[0]).read().splitlines()
|
||||
|
||||
for ratx in ("_ratS ", "_ratA "):
|
||||
print(ratx)
|
||||
header = None
|
||||
header_row = None
|
||||
data_row = None
|
||||
data_row_buffer = []
|
||||
|
||||
def show():
|
||||
if header_row:
|
||||
if header is None:
|
||||
print(",".join(header_row))
|
||||
else:
|
||||
assert header == header_row
|
||||
if data_row is not None:
|
||||
print(",".join(data_row))
|
||||
data_row_buffer.append(data_row)
|
||||
return header_row
|
||||
|
||||
for line in log_lines:
|
||||
if line.endswith(" data_size"):
|
||||
header = show()
|
||||
flds = line.split()
|
||||
assert len(flds) == 2
|
||||
header_row = ["data_size"]
|
||||
data_row = [flds[0]]
|
||||
elif line.endswith(" call_repetitions"):
|
||||
flds = line.split()
|
||||
assert len(flds) == 2
|
||||
header_row.append("calls")
|
||||
data_row.append(flds[0])
|
||||
header_row.append("up")
|
||||
data_row.append("1.000")
|
||||
elif line[2:].startswith(ratx):
|
||||
flds = line.split()
|
||||
assert len(flds) == 4
|
||||
header_row.append(line[:2])
|
||||
data_row.append(flds[2])
|
||||
show()
|
||||
|
||||
print("Scaled to last column:")
|
||||
print(",".join(header_row))
|
||||
for data_row in data_row_buffer:
|
||||
data_row_rescaled = data_row[:2]
|
||||
unit = float(data_row[-1])
|
||||
for fld in data_row[2:]:
|
||||
data_row_rescaled.append("%.3f" % (float(fld) / unit))
|
||||
print(",".join(data_row_rescaled))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
run(args=sys.argv[1:])
|
55
ubench/number_bucket.h
Normal file
55
ubench/number_bucket.h
Normal file
@ -0,0 +1,55 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstddef>
|
||||
#include <exception>
|
||||
#include <iostream>
|
||||
#include <vector>
|
||||
|
||||
namespace pybind11_ubench {
|
||||
|
||||
template <int Serial>
|
||||
struct number_bucket {
|
||||
std::vector<double> data;
|
||||
|
||||
explicit number_bucket(std::size_t data_size = 0) : data(data_size, 1.0) {}
|
||||
|
||||
double sum() const {
|
||||
std::size_t n = 0;
|
||||
double s = 0;
|
||||
const double *a = &*data.begin();
|
||||
const double *e = &*data.end();
|
||||
while (a != e) {
|
||||
s += *a++;
|
||||
n++;
|
||||
}
|
||||
if (n != data.size()) {
|
||||
std::cerr << "Internal consistency failure (sum)." << std::endl;
|
||||
std::terminate();
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
std::size_t add(const number_bucket &other) {
|
||||
if (other.data.size() != data.size()) {
|
||||
std::cerr << "Incompatible data sizes (add)." << std::endl;
|
||||
std::terminate();
|
||||
}
|
||||
std::size_t n = 0;
|
||||
double *a = &*data.begin();
|
||||
const double *e = &*data.end();
|
||||
const double *b = &*other.data.begin();
|
||||
while (a != e) {
|
||||
*a++ += *b++;
|
||||
n++;
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
private:
|
||||
number_bucket(const number_bucket &) = delete;
|
||||
number_bucket(number_bucket &&) = delete;
|
||||
number_bucket &operator=(const number_bucket &) = delete;
|
||||
number_bucket &operator=(number_bucket &&) = delete;
|
||||
};
|
||||
|
||||
} // namespace pybind11_ubench
|
6
ubench/python/number_bucket.clif
Normal file
6
ubench/python/number_bucket.clif
Normal file
@ -0,0 +1,6 @@
|
||||
from "pybind11/ubench/number_bucket.h":
|
||||
namespace `pybind11_ubench`:
|
||||
class `number_bucket<0>` as number_bucket_pc:
|
||||
def __init__(self, data_size: int = default)
|
||||
def sum(self) -> float
|
||||
def add(self, other: number_bucket_pc) -> int
|
Loading…
Reference in New Issue
Block a user