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 commit 98f1bbb800).

* 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_tests

a2c2f88174

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 commit 95425f13d6.

* 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 commit 249df7cbdb. 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 commit 249df7cbdb 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 commit 5d4b6890a3.

* Removing unused smart_holder_type_caster_load::loaded_as_rvalue_ref (it was an oversight that it was not removed with commit 23036a45eb).

* Removing py::classu, because it does not seem useful enough.

* Reverting commit 6349531306 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:
Ralf W. Grosse-Kunstleve 2021-02-23 21:50:42 -08:00 committed by GitHub
parent 74a767d429
commit 1bafd5db5f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
39 changed files with 3111 additions and 272 deletions

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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:

View File

@ -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) }

View File

@ -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 {

View 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

View 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)

View 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)

View File

@ -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.

View 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)

View File

@ -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;

View File

@ -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)

View 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);
}

View 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);
}

View 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);
}

View File

@ -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",
}

View 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)

View 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).");
}

View File

@ -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,

View 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

View 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"

View 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.
}));
}

View 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"
)

View 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

View 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

View 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!"
)

View 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

View 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

View File

@ -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:

View File

@ -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");

View File

@ -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 {};

View File

@ -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)

View File

@ -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",
)

View 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
View 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:])

View 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
View 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

View 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