mirror of
https://github.com/pybind/pybind11.git
synced 2024-11-11 08:03:55 +00:00
Merge 1baa98b689
into 1f8b4a7f1a
This commit is contained in:
commit
cce935483a
@ -12,6 +12,17 @@ template <op_id id, op_type ot, typename L, typename R>
|
||||
template <detail::op_id id, detail::op_type ot, typename L, typename R, typename... Extra>
|
||||
class_ &def(const detail::op_<id, ot, L, R> &op, const Extra &...extra) {
|
||||
class_ &def_cast(const detail::op_<id, ot, L, R> &op, const Extra &...extra) {
|
||||
int valu;
|
||||
explicit movable_int(int v) : valu{v} {}
|
||||
movable_int(movable_int &&other) noexcept : valu(other.valu) { other.valu = 91; }
|
||||
explicit indestructible_int(int v) : valu{v} {}
|
||||
REQUIRE(hld.as_raw_ptr_unowned<zombie>()->valu == 19);
|
||||
REQUIRE(othr.valu == 19);
|
||||
REQUIRE(orig.valu == 91);
|
||||
(m.pass_valu, "Valu", "pass_valu:Valu(_MvCtor)*_CpCtor"),
|
||||
atyp_valu rtrn_valu() { atyp_valu obj{"Valu"}; return obj; }
|
||||
assert m.atyp_valu().get_mtxt() == "Valu"
|
||||
// valu(e), ref(erence), ptr or p (pointer), r = rvalue, m = mutable, c = const,
|
||||
@pytest.mark.parametrize("access", ["ro", "rw", "static_ro", "static_rw"])
|
||||
struct IntStruct {
|
||||
explicit IntStruct(int v) : value(v){};
|
||||
|
30
.github/workflows/ci.yml
vendored
30
.github/workflows/ci.yml
vendored
@ -7,6 +7,7 @@ on:
|
||||
branches:
|
||||
- master
|
||||
- stable
|
||||
- smart_holder
|
||||
- v*
|
||||
|
||||
permissions: read-all
|
||||
@ -67,7 +68,34 @@ jobs:
|
||||
# Extra ubuntu latest job
|
||||
- runs-on: ubuntu-latest
|
||||
python: '3.11'
|
||||
|
||||
# Exercise PYBIND11_USE_SMART_HOLDER_AS_DEFAULT
|
||||
# with recent (or ideally latest) released Python version.
|
||||
- runs-on: ubuntu-latest
|
||||
python: '3.12'
|
||||
args: >
|
||||
-DCMAKE_CXX_FLAGS="-DPYBIND11_USE_SMART_HOLDER_AS_DEFAULT"
|
||||
- runs-on: macos-13
|
||||
python: '3.12'
|
||||
args: >
|
||||
-DCMAKE_CXX_FLAGS="-DPYBIND11_USE_SMART_HOLDER_AS_DEFAULT"
|
||||
- runs-on: windows-2022
|
||||
python: '3.12'
|
||||
args: >
|
||||
-DCMAKE_CXX_FLAGS="/DPYBIND11_USE_SMART_HOLDER_AS_DEFAULT /GR /EHsc"
|
||||
# Exercise PYBIND11_SMART_HOLDER_DISABLE
|
||||
# with recent (or ideally latest) released Python version.
|
||||
- runs-on: ubuntu-latest
|
||||
python: '3.12'
|
||||
args: >
|
||||
-DCMAKE_CXX_FLAGS="-DPYBIND11_SMART_HOLDER_DISABLE"
|
||||
- runs-on: macos-13
|
||||
python: '3.12'
|
||||
args: >
|
||||
-DCMAKE_CXX_FLAGS="-DPYBIND11_SMART_HOLDER_DISABLE"
|
||||
- runs-on: windows-2022
|
||||
python: '3.12'
|
||||
args: >
|
||||
-DCMAKE_CXX_FLAGS="/DPYBIND11_SMART_HOLDER_DISABLE /GR /EHsc"
|
||||
|
||||
name: "🐍 ${{ matrix.python }} • ${{ matrix.runs-on }} • x64 ${{ matrix.args }}"
|
||||
runs-on: ${{ matrix.runs-on }}
|
||||
|
1
.github/workflows/configure.yml
vendored
1
.github/workflows/configure.yml
vendored
@ -7,6 +7,7 @@ on:
|
||||
branches:
|
||||
- master
|
||||
- stable
|
||||
- smart_holder
|
||||
- v*
|
||||
|
||||
permissions:
|
||||
|
1
.github/workflows/emscripten.yaml
vendored
1
.github/workflows/emscripten.yaml
vendored
@ -6,6 +6,7 @@ on:
|
||||
branches:
|
||||
- master
|
||||
- stable
|
||||
- smart_holder
|
||||
- v*
|
||||
|
||||
concurrency:
|
||||
|
1
.github/workflows/format.yml
vendored
1
.github/workflows/format.yml
vendored
@ -10,6 +10,7 @@ on:
|
||||
branches:
|
||||
- master
|
||||
- stable
|
||||
- smart_holder
|
||||
- "v*"
|
||||
|
||||
permissions:
|
||||
|
1
.github/workflows/pip.yml
vendored
1
.github/workflows/pip.yml
vendored
@ -7,6 +7,7 @@ on:
|
||||
branches:
|
||||
- master
|
||||
- stable
|
||||
- smart_holder
|
||||
- v*
|
||||
release:
|
||||
types:
|
||||
|
@ -76,6 +76,7 @@ repos:
|
||||
- id: mixed-line-ending
|
||||
- id: requirements-txt-fixer
|
||||
- id: trailing-whitespace
|
||||
exclude: \.patch?$
|
||||
|
||||
# Also code format the docs
|
||||
- repo: https://github.com/adamchainz/blacken-docs
|
||||
@ -90,6 +91,7 @@ repos:
|
||||
rev: "v1.5.5"
|
||||
hooks:
|
||||
- id: remove-tabs
|
||||
exclude: (^docs/.*|\.patch)?$
|
||||
|
||||
# Avoid directional quotes
|
||||
- repo: https://github.com/sirosen/texthooks
|
||||
|
@ -131,10 +131,13 @@ set(PYBIND11_HEADERS
|
||||
include/pybind11/detail/common.h
|
||||
include/pybind11/detail/cpp_conduit.h
|
||||
include/pybind11/detail/descr.h
|
||||
include/pybind11/detail/dynamic_raw_ptr_cast_if_possible.h
|
||||
include/pybind11/detail/init.h
|
||||
include/pybind11/detail/internals.h
|
||||
include/pybind11/detail/struct_smart_holder.h
|
||||
include/pybind11/detail/type_caster_base.h
|
||||
include/pybind11/detail/typeid.h
|
||||
include/pybind11/detail/using_smart_holder.h
|
||||
include/pybind11/detail/value_and_holder.h
|
||||
include/pybind11/detail/exception_translation.h
|
||||
include/pybind11/attr.h
|
||||
@ -158,9 +161,11 @@ 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
|
||||
include/pybind11/stl/filesystem.h
|
||||
include/pybind11/trampoline_self_life_support.h
|
||||
include/pybind11/type_caster_pyobject_ptr.h
|
||||
include/pybind11/typing.h
|
||||
include/pybind11/warnings.h)
|
||||
|
@ -1,4 +1,6 @@
|
||||
prune tests
|
||||
prune ubench
|
||||
include README_smart_holder.rst
|
||||
recursive-include pybind11/include/pybind11 *.h
|
||||
recursive-include pybind11 *.py
|
||||
recursive-include pybind11 py.typed
|
||||
|
@ -13,6 +13,10 @@
|
||||
|
||||
.. start
|
||||
|
||||
.. Note::
|
||||
|
||||
This is the pybind11 **smart_holder** branch. Please refer to
|
||||
``README_smart_holder.rst`` for branch-specific information.
|
||||
|
||||
**pybind11** is a lightweight header-only library that exposes C++ types
|
||||
in Python and vice versa, mainly to create Python bindings of existing
|
||||
|
123
README_smart_holder.rst
Normal file
123
README_smart_holder.rst
Normal file
@ -0,0 +1,123 @@
|
||||
==============================
|
||||
pybind11 — smart_holder branch
|
||||
==============================
|
||||
|
||||
|
||||
Overview
|
||||
========
|
||||
|
||||
- The smart_holder branch is a strict superset of the pybind11 master branch.
|
||||
Everything that works with the master branch is expected to work exactly the
|
||||
same with the smart_holder branch.
|
||||
|
||||
- Activating the smart_holder functionality for a given C++ type ``T`` is as
|
||||
easy as changing ``py::class_<T>`` to ``py::classh<T>`` in client code.
|
||||
|
||||
- The ``py::classh<T>`` functionality includes
|
||||
|
||||
* support for **two-way** Python/C++ conversions for both
|
||||
``std::unique_ptr<T>`` and ``std::shared_ptr<T>`` **simultaneously**.
|
||||
— In contrast, ``py::class_<T>`` only supports one-way C++-to-Python
|
||||
conversions for ``std::unique_ptr<T>``, or alternatively two-way
|
||||
Python/C++ conversions for ``std::shared_ptr<T>``, which then excludes
|
||||
the one-way C++-to-Python ``std::unique_ptr<T>`` conversions (this
|
||||
manifests itself through undefined runtime behavior).
|
||||
|
||||
* passing a Python object back to C++ via ``std::unique_ptr<T>``, safely
|
||||
**disowning** the Python object.
|
||||
|
||||
* safely passing `"trampoline"
|
||||
<https://pybind11.readthedocs.io/en/stable/advanced/classes.html#overriding-virtual-functions-in-python>`_
|
||||
objects (objects with C++ virtual function overrides implemented in
|
||||
Python) via ``std::unique_ptr<T>`` or ``std::shared_ptr<T>`` back to C++:
|
||||
associated Python objects are automatically kept alive for the lifetime
|
||||
of the smart-pointer.
|
||||
|
||||
Note: As of `PR #5257 <https://github.com/pybind/pybind11/pull/5257>`_
|
||||
the smart_holder functionality is fully baked into pybind11.
|
||||
Prior to PR #5257 the smart_holder implementation was an "add-on", which made
|
||||
it necessary to use a ``PYBIND11_SMART_HOLDER_TYPE_CASTERS`` macro. This macro
|
||||
still exists for backward compatibility, but is now a no-op. The trade-off
|
||||
for this convenience is that the ``PYBIND11_INTERNALS_VERSION`` needed to be
|
||||
changed. Consequently, Python extension modules built with the smart_holder
|
||||
branch no longer interoperate with extension modules built with the pybind11
|
||||
master branch. If cross-extension-module interoperability is required, all
|
||||
extension modules involved must be built with the smart_holder branch.
|
||||
— Probably, most extension modules do not require cross-extension-module
|
||||
interoperability, but exceptions to this are quite common.
|
||||
|
||||
|
||||
What is fundamentally different?
|
||||
--------------------------------
|
||||
|
||||
- Classic pybind11 has the concept of "smart-pointer is holder".
|
||||
Interoperability between smart-pointers is completely missing. For example,
|
||||
with ``py::class_<T, std::shared_ptr<T>>``, ``return``-ing a
|
||||
``std::unique_ptr<T>`` leads to undefined runtime behavior
|
||||
(`#1138 <https://github.com/pybind/pybind11/issues/1138>`_).
|
||||
A `systematic analysis can be found here
|
||||
<https://github.com/pybind/pybind11/pull/2672#issuecomment-748392993>`_.
|
||||
|
||||
- ``py::smart_holder`` has a richer concept in comparison, with well-defined
|
||||
runtime behavior in all situations. ``py::smart_holder`` "knows" about both
|
||||
``std::unique_ptr<T>`` and ``std::shared_ptr<T>``, and how they interoperate.
|
||||
|
||||
|
||||
What motivated the development of the smart_holder code?
|
||||
--------------------------------------------------------
|
||||
|
||||
- The original context was retooling of `PyCLIF
|
||||
<https://github.com/google/clif/>`_, to use pybind11 underneath,
|
||||
instead of directly targeting the Python C API. Essentially the smart_holder
|
||||
branch is porting established PyCLIF functionality into pybind11. (However,
|
||||
this work also led to bug fixes in PyCLIF.)
|
||||
|
||||
|
||||
Installation
|
||||
============
|
||||
|
||||
Currently ``git clone`` is the only option. We do not have released packages.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
git clone --branch smart_holder https://github.com/pybind/pybind11.git
|
||||
|
||||
Everything else is exactly identical to using the default (master) branch.
|
||||
|
||||
|
||||
Trampolines and std::unique_ptr
|
||||
-------------------------------
|
||||
|
||||
A pybind11 `"trampoline"
|
||||
<https://pybind11.readthedocs.io/en/stable/advanced/classes.html#overriding-virtual-functions-in-python>`_
|
||||
is a C++ helper class with virtual function overrides that transparently
|
||||
call back from C++ into Python. To enable safely passing a ``std::unique_ptr``
|
||||
to a trampoline object between Python and C++, the trampoline class must
|
||||
inherit from ``py::trampoline_self_life_support``, for example:
|
||||
|
||||
.. code-block:: cpp
|
||||
|
||||
class PyAnimal : public Animal, public py::trampoline_self_life_support {
|
||||
...
|
||||
};
|
||||
|
||||
This is the only difference compared to classic pybind11. A fairly
|
||||
minimal but complete example is tests/test_class_sh_trampoline_unique_ptr.cpp.
|
||||
|
||||
|
||||
Related links
|
||||
=============
|
||||
|
||||
* The smart_holder branch addresses issue
|
||||
`#1138 <https://github.com/pybind/pybind11/issues/1138>`_ and
|
||||
the ten issues enumerated in the `description of PR 2839
|
||||
<https://github.com/pybind/pybind11/pull/2839#issue-564808678>`_.
|
||||
|
||||
* `Description of PR #2672
|
||||
<https://github.com/pybind/pybind11/pull/2672#issue-522688184>`_, from which
|
||||
the smart_holder branch was created.
|
||||
|
||||
* Small `slide deck
|
||||
<https://docs.google.com/presentation/d/1r7auDN0x-b6uf-XCvUnZz6z09raasRcCHBMVDh7PsnQ/>`_
|
||||
presented in meeting with pybind11 maintainers on Feb 22, 2021. Slides 5
|
||||
and 6 show performance comparisons. (These are outdated but probably not far off.)
|
@ -1,6 +1,13 @@
|
||||
Smart pointers
|
||||
##############
|
||||
|
||||
.. Note::
|
||||
|
||||
This is the pybind11 **smart_holder** branch, but the information
|
||||
below has NOT been updated accordingly yet. Please refer to
|
||||
``README_smart_holder.rst`` under the top-level pybind11 directory
|
||||
for updated information about smart pointers.
|
||||
|
||||
std::unique_ptr
|
||||
===============
|
||||
|
||||
|
@ -331,6 +331,10 @@ struct type_record {
|
||||
/// Is the class inheritable from python classes?
|
||||
bool is_final : 1;
|
||||
|
||||
#ifdef PYBIND11_HAS_INTERNALS_WITH_SMART_HOLDER_SUPPORT
|
||||
holder_enum_t holder_enum_v = holder_enum_t::undefined;
|
||||
#endif
|
||||
|
||||
PYBIND11_NOINLINE void add_base(const std::type_info &base, void *(*caster)(void *) ) {
|
||||
auto *base_info = detail::get_type_info(base, false);
|
||||
if (!base_info) {
|
||||
|
@ -754,6 +754,7 @@ struct holder_helper {
|
||||
static auto get(const T &p) -> decltype(p.get()) { return p.get(); }
|
||||
};
|
||||
|
||||
// SMART_HOLDER_BAKEIN_FOLLOW_ON: Rewrite comment, with reference to shared_ptr specialization.
|
||||
/// Type caster for holder types like std::shared_ptr, etc.
|
||||
/// The SFINAE hook is provided to help work around the current lack of support
|
||||
/// for smart-pointer interoperability. Please consider it an implementation
|
||||
@ -835,10 +836,145 @@ protected:
|
||||
holder_type holder;
|
||||
};
|
||||
|
||||
#ifdef PYBIND11_SMART_HOLDER_ENABLED
|
||||
|
||||
template <typename, typename SFINAE = void>
|
||||
struct copyable_holder_caster_shared_ptr_with_smart_holder_support_enabled : std::true_type {};
|
||||
|
||||
// SMART_HOLDER_BAKEIN_FOLLOW_ON: Refactor copyable_holder_caster to reduce code duplication.
|
||||
template <typename type>
|
||||
struct copyable_holder_caster<
|
||||
type,
|
||||
std::shared_ptr<type>,
|
||||
enable_if_t<copyable_holder_caster_shared_ptr_with_smart_holder_support_enabled<type>::value>>
|
||||
: public type_caster_base<type> {
|
||||
public:
|
||||
using base = type_caster_base<type>;
|
||||
static_assert(std::is_base_of<base, type_caster<type>>::value,
|
||||
"Holder classes are only supported for custom types");
|
||||
using base::base;
|
||||
using base::cast;
|
||||
using base::typeinfo;
|
||||
using base::value;
|
||||
|
||||
bool load(handle src, bool convert) {
|
||||
if (base::template load_impl<copyable_holder_caster<type, std::shared_ptr<type>>>(
|
||||
src, convert)) {
|
||||
sh_load_helper.maybe_set_python_instance_is_alias(src);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
explicit operator std::shared_ptr<type> *() {
|
||||
if (typeinfo->holder_enum_v == detail::holder_enum_t::smart_holder) {
|
||||
pybind11_fail("Passing `std::shared_ptr<T> *` from Python to C++ is not supported "
|
||||
"(inherently unsafe).");
|
||||
}
|
||||
return std::addressof(shared_ptr_storage);
|
||||
}
|
||||
|
||||
explicit operator std::shared_ptr<type> &() {
|
||||
if (typeinfo->holder_enum_v == detail::holder_enum_t::smart_holder) {
|
||||
shared_ptr_storage = sh_load_helper.load_as_shared_ptr(value);
|
||||
}
|
||||
return shared_ptr_storage;
|
||||
}
|
||||
|
||||
static handle
|
||||
cast(const std::shared_ptr<type> &src, return_value_policy policy, handle parent) {
|
||||
const auto *ptr = src.get();
|
||||
auto st = type_caster_base<type>::src_and_type(ptr);
|
||||
if (st.second == nullptr) {
|
||||
return handle(); // no type info: error will be set already
|
||||
}
|
||||
if (st.second->holder_enum_v == detail::holder_enum_t::smart_holder) {
|
||||
return smart_holder_type_caster_support::smart_holder_from_shared_ptr(
|
||||
src, policy, parent, st);
|
||||
}
|
||||
return type_caster_base<type>::cast_holder(ptr, &src);
|
||||
}
|
||||
|
||||
// This function will succeed even if the `responsible_parent` does not own the
|
||||
// wrapped C++ object directly.
|
||||
// It is the responsibility of the caller to ensure that the `responsible_parent`
|
||||
// has a `keep_alive` relationship with the owner of the wrapped C++ object, or
|
||||
// that the wrapped C++ object lives for the duration of the process.
|
||||
static std::shared_ptr<type> shared_ptr_with_responsible_parent(handle responsible_parent) {
|
||||
copyable_holder_caster loader;
|
||||
loader.load(responsible_parent, /*convert=*/false);
|
||||
assert(loader.typeinfo->holder_enum_v == detail::holder_enum_t::smart_holder);
|
||||
return loader.sh_load_helper.load_as_shared_ptr(loader.value, responsible_parent);
|
||||
}
|
||||
|
||||
protected:
|
||||
friend class type_caster_generic;
|
||||
void check_holder_compat() {
|
||||
if (typeinfo->default_holder) {
|
||||
throw cast_error("Unable to load a custom holder type from a default-holder instance");
|
||||
}
|
||||
}
|
||||
|
||||
void load_value(value_and_holder &&v_h) {
|
||||
if (typeinfo->holder_enum_v == detail::holder_enum_t::smart_holder) {
|
||||
sh_load_helper.loaded_v_h = v_h;
|
||||
sh_load_helper.was_populated = true;
|
||||
value = sh_load_helper.get_void_ptr_or_nullptr();
|
||||
return;
|
||||
}
|
||||
if (v_h.holder_constructed()) {
|
||||
value = v_h.value_ptr();
|
||||
shared_ptr_storage = v_h.template holder<std::shared_ptr<type>>();
|
||||
return;
|
||||
}
|
||||
throw cast_error("Unable to cast from non-held to held instance (T& to Holder<T>) "
|
||||
# if !defined(PYBIND11_DETAILED_ERROR_MESSAGES)
|
||||
"(#define PYBIND11_DETAILED_ERROR_MESSAGES or compile in debug mode for "
|
||||
"type information)");
|
||||
# else
|
||||
"of type '"
|
||||
+ type_id<std::shared_ptr<type>>() + "''");
|
||||
# endif
|
||||
}
|
||||
|
||||
template <typename T = std::shared_ptr<type>,
|
||||
detail::enable_if_t<!std::is_constructible<T, const T &, type *>::value, int> = 0>
|
||||
bool try_implicit_casts(handle, bool) {
|
||||
return false;
|
||||
}
|
||||
|
||||
template <typename T = std::shared_ptr<type>,
|
||||
detail::enable_if_t<std::is_constructible<T, const T &, type *>::value, int> = 0>
|
||||
bool try_implicit_casts(handle src, bool convert) {
|
||||
for (auto &cast : typeinfo->implicit_casts) {
|
||||
copyable_holder_caster sub_caster(*cast.first);
|
||||
if (sub_caster.load(src, convert)) {
|
||||
value = cast.second(sub_caster.value);
|
||||
if (typeinfo->holder_enum_v == detail::holder_enum_t::smart_holder) {
|
||||
sh_load_helper.loaded_v_h = sub_caster.sh_load_helper.loaded_v_h;
|
||||
} else {
|
||||
shared_ptr_storage
|
||||
= std::shared_ptr<type>(sub_caster.shared_ptr_storage, (type *) value);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool try_direct_conversions(handle) { return false; }
|
||||
|
||||
smart_holder_type_caster_support::load_helper<remove_cv_t<type>> sh_load_helper; // Const2Mutbl
|
||||
std::shared_ptr<type> shared_ptr_storage;
|
||||
};
|
||||
|
||||
#endif // PYBIND11_SMART_HOLDER_ENABLED
|
||||
|
||||
/// 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>> {};
|
||||
|
||||
// SMART_HOLDER_BAKEIN_FOLLOW_ON: Rewrite comment, with reference to unique_ptr specialization.
|
||||
/// Type caster for holder types like std::unique_ptr.
|
||||
/// Please consider the SFINAE hook an implementation detail, as explained
|
||||
/// in the comment for the copyable_holder_caster.
|
||||
@ -854,6 +990,147 @@ struct move_only_holder_caster {
|
||||
static constexpr auto name = type_caster_base<type>::name;
|
||||
};
|
||||
|
||||
#ifdef PYBIND11_SMART_HOLDER_ENABLED
|
||||
|
||||
template <typename, typename SFINAE = void>
|
||||
struct move_only_holder_caster_unique_ptr_with_smart_holder_support_enabled : std::true_type {};
|
||||
|
||||
// SMART_HOLDER_BAKEIN_FOLLOW_ON: Refactor move_only_holder_caster to reduce code duplication.
|
||||
template <typename type, typename deleter>
|
||||
struct move_only_holder_caster<
|
||||
type,
|
||||
std::unique_ptr<type, deleter>,
|
||||
enable_if_t<move_only_holder_caster_unique_ptr_with_smart_holder_support_enabled<type>::value>>
|
||||
: public type_caster_base<type> {
|
||||
public:
|
||||
using base = type_caster_base<type>;
|
||||
static_assert(std::is_base_of<base, type_caster<type>>::value,
|
||||
"Holder classes are only supported for custom types");
|
||||
using base::base;
|
||||
using base::cast;
|
||||
using base::typeinfo;
|
||||
using base::value;
|
||||
|
||||
static handle
|
||||
cast(std::unique_ptr<type, deleter> &&src, return_value_policy policy, handle parent) {
|
||||
auto *ptr = src.get();
|
||||
auto st = type_caster_base<type>::src_and_type(ptr);
|
||||
if (st.second == nullptr) {
|
||||
return handle(); // no type info: error will be set already
|
||||
}
|
||||
if (st.second->holder_enum_v == detail::holder_enum_t::smart_holder) {
|
||||
return smart_holder_type_caster_support::smart_holder_from_unique_ptr(
|
||||
std::move(src), policy, parent, st);
|
||||
}
|
||||
return type_caster_generic::cast(st.first,
|
||||
return_value_policy::take_ownership,
|
||||
{},
|
||||
st.second,
|
||||
nullptr,
|
||||
nullptr,
|
||||
std::addressof(src));
|
||||
}
|
||||
|
||||
static handle
|
||||
cast(const std::unique_ptr<type, deleter> &src, return_value_policy policy, handle parent) {
|
||||
if (!src) {
|
||||
return none().release();
|
||||
}
|
||||
if (policy == return_value_policy::automatic) {
|
||||
policy = return_value_policy::reference_internal;
|
||||
}
|
||||
if (policy != return_value_policy::reference_internal) {
|
||||
throw cast_error("Invalid return_value_policy for const unique_ptr&");
|
||||
}
|
||||
return type_caster_base<type>::cast(src.get(), policy, parent);
|
||||
}
|
||||
|
||||
bool load(handle src, bool convert) {
|
||||
if (base::template load_impl<
|
||||
move_only_holder_caster<type, std::unique_ptr<type, deleter>>>(src, convert)) {
|
||||
sh_load_helper.maybe_set_python_instance_is_alias(src);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void load_value(value_and_holder &&v_h) {
|
||||
if (typeinfo->holder_enum_v == detail::holder_enum_t::smart_holder) {
|
||||
sh_load_helper.loaded_v_h = v_h;
|
||||
sh_load_helper.loaded_v_h.type = typeinfo;
|
||||
sh_load_helper.was_populated = true;
|
||||
value = sh_load_helper.get_void_ptr_or_nullptr();
|
||||
return;
|
||||
}
|
||||
pybind11_fail(
|
||||
"Passing `std::unique_ptr<T>` from Python to C++ requires `py::classh` (with T = "
|
||||
+ clean_type_id(typeinfo->cpptype->name()) + ")");
|
||||
}
|
||||
|
||||
template <typename T_>
|
||||
using cast_op_type
|
||||
= conditional_t<std::is_same<typename std::remove_volatile<T_>::type,
|
||||
const std::unique_ptr<type, deleter> &>::value
|
||||
|| std::is_same<typename std::remove_volatile<T_>::type,
|
||||
const std::unique_ptr<const type, deleter> &>::value,
|
||||
const std::unique_ptr<type, deleter> &,
|
||||
std::unique_ptr<type, deleter>>;
|
||||
|
||||
explicit operator std::unique_ptr<type, deleter>() {
|
||||
if (typeinfo->holder_enum_v == detail::holder_enum_t::smart_holder) {
|
||||
return sh_load_helper.template load_as_unique_ptr<deleter>(value);
|
||||
}
|
||||
pybind11_fail("Expected to be UNREACHABLE: " __FILE__ ":" PYBIND11_TOSTRING(__LINE__));
|
||||
}
|
||||
|
||||
explicit operator const std::unique_ptr<type, deleter> &() {
|
||||
if (typeinfo->holder_enum_v == detail::holder_enum_t::smart_holder) {
|
||||
// Get shared_ptr to ensure that the Python object is not disowned elsewhere.
|
||||
shared_ptr_storage = sh_load_helper.load_as_shared_ptr(value);
|
||||
// Build a temporary unique_ptr that is meant to never expire.
|
||||
unique_ptr_storage = std::shared_ptr<std::unique_ptr<type, deleter>>(
|
||||
new std::unique_ptr<type, deleter>{
|
||||
sh_load_helper.template load_as_const_unique_ptr<deleter>(
|
||||
shared_ptr_storage.get())},
|
||||
[](std::unique_ptr<type, deleter> *ptr) {
|
||||
if (!ptr) {
|
||||
pybind11_fail("FATAL: `const std::unique_ptr<T, D> &` was disowned "
|
||||
"(EXPECT UNDEFINED BEHAVIOR).");
|
||||
}
|
||||
(void) ptr->release();
|
||||
delete ptr;
|
||||
});
|
||||
return *unique_ptr_storage;
|
||||
}
|
||||
pybind11_fail("Expected to be UNREACHABLE: " __FILE__ ":" PYBIND11_TOSTRING(__LINE__));
|
||||
}
|
||||
|
||||
bool try_implicit_casts(handle src, bool convert) {
|
||||
for (auto &cast : typeinfo->implicit_casts) {
|
||||
move_only_holder_caster sub_caster(*cast.first);
|
||||
if (sub_caster.load(src, convert)) {
|
||||
value = cast.second(sub_caster.value);
|
||||
if (typeinfo->holder_enum_v == detail::holder_enum_t::smart_holder) {
|
||||
sh_load_helper.loaded_v_h = sub_caster.sh_load_helper.loaded_v_h;
|
||||
} else {
|
||||
pybind11_fail("Expected to be UNREACHABLE: " __FILE__
|
||||
":" PYBIND11_TOSTRING(__LINE__));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool try_direct_conversions(handle) { return false; }
|
||||
|
||||
smart_holder_type_caster_support::load_helper<remove_cv_t<type>> sh_load_helper; // Const2Mutbl
|
||||
std::shared_ptr<type> shared_ptr_storage; // Serves as a pseudo lock.
|
||||
std::shared_ptr<std::unique_ptr<type, deleter>> unique_ptr_storage;
|
||||
};
|
||||
|
||||
#endif // PYBIND11_SMART_HOLDER_ENABLED
|
||||
|
||||
template <typename type, typename deleter>
|
||||
class type_caster<std::unique_ptr<type, deleter>>
|
||||
: public move_only_holder_caster<type, std::unique_ptr<type, deleter>> {};
|
||||
@ -885,10 +1162,16 @@ struct always_construct_holder {
|
||||
template <typename base, typename holder>
|
||||
struct is_holder_type
|
||||
: std::is_base_of<detail::type_caster_holder<base, holder>, detail::type_caster<holder>> {};
|
||||
// Specialization for always-supported unique_ptr holders:
|
||||
|
||||
// Specializations for always-supported holders:
|
||||
template <typename base, typename deleter>
|
||||
struct is_holder_type<base, std::unique_ptr<base, deleter>> : std::true_type {};
|
||||
|
||||
#ifdef PYBIND11_SMART_HOLDER_ENABLED
|
||||
template <typename base>
|
||||
struct is_holder_type<base, smart_holder> : std::true_type {};
|
||||
#endif
|
||||
|
||||
#ifdef PYBIND11_DISABLE_HANDLE_TYPE_NAME_DEFAULT_IMPLEMENTATION // See PR #4888
|
||||
|
||||
// This leads to compilation errors if a specialization is missing.
|
||||
|
@ -433,6 +433,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:
|
||||
|
@ -9,13 +9,13 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#define PYBIND11_VERSION_MAJOR 2
|
||||
#define PYBIND11_VERSION_MINOR 14
|
||||
#define PYBIND11_VERSION_MAJOR 3
|
||||
#define PYBIND11_VERSION_MINOR 0
|
||||
#define PYBIND11_VERSION_PATCH 0.dev1
|
||||
|
||||
// Similar to Python's convention: https://docs.python.org/3/c-api/apiabiversion.html
|
||||
// Additional convention: 0xD = dev
|
||||
#define PYBIND11_VERSION_HEX 0x020E00D1
|
||||
#define PYBIND11_VERSION_HEX 0x030000D1
|
||||
|
||||
// Define some generic pybind11 helper macros for warning management.
|
||||
//
|
||||
@ -640,6 +640,11 @@ struct instance {
|
||||
bool simple_instance_registered : 1;
|
||||
/// If true, get_internals().patients has an entry for this object
|
||||
bool has_patients : 1;
|
||||
// Cannot use PYBIND11_INTERNALS_VERSION >= 6 here without refactoring.
|
||||
#if PYBIND11_VERSION_MAJOR >= 3
|
||||
/// If true, this Python object needs to be kept alive for the lifetime of the C++ value.
|
||||
bool is_alias : 1;
|
||||
#endif
|
||||
|
||||
/// Initializes all of the above type/values/holders data (but not the instance values
|
||||
/// themselves)
|
||||
|
39
include/pybind11/detail/dynamic_raw_ptr_cast_if_possible.h
Normal file
39
include/pybind11/detail/dynamic_raw_ptr_cast_if_possible.h
Normal file
@ -0,0 +1,39 @@
|
||||
// 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>
|
||||
|
||||
PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE)
|
||||
PYBIND11_NAMESPACE_BEGIN(detail)
|
||||
|
||||
template <typename To, typename From, typename SFINAE = void>
|
||||
struct dynamic_raw_ptr_cast_is_possible : std::false_type {};
|
||||
|
||||
template <typename To, typename From>
|
||||
struct dynamic_raw_ptr_cast_is_possible<
|
||||
To,
|
||||
From,
|
||||
detail::enable_if_t<!std::is_same<To, void>::value && std::is_polymorphic<From>::value>>
|
||||
: std::true_type {};
|
||||
|
||||
template <typename To,
|
||||
typename From,
|
||||
detail::enable_if_t<!dynamic_raw_ptr_cast_is_possible<To, From>::value, int> = 0>
|
||||
To *dynamic_raw_ptr_cast_if_possible(From * /*ptr*/) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
template <typename To,
|
||||
typename From,
|
||||
detail::enable_if_t<dynamic_raw_ptr_cast_is_possible<To, From>::value, int> = 0>
|
||||
To *dynamic_raw_ptr_cast_if_possible(From *ptr) {
|
||||
return dynamic_cast<To *>(ptr);
|
||||
}
|
||||
|
||||
PYBIND11_NAMESPACE_END(detail)
|
||||
PYBIND11_NAMESPACE_END(PYBIND11_NAMESPACE)
|
@ -10,6 +10,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "class.h"
|
||||
#include "using_smart_holder.h"
|
||||
|
||||
PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE)
|
||||
|
||||
@ -155,7 +156,7 @@ void construct(value_and_holder &v_h, Alias<Class> *alias_ptr, bool) {
|
||||
// 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<!is_smart_holder<Holder<Class>>::value, int> = 0>
|
||||
void construct(value_and_holder &v_h, Holder<Class> holder, bool need_alias) {
|
||||
PYBIND11_WORKAROUND_INCORRECT_MSVC_C4100(need_alias);
|
||||
auto *ptr = holder_helper<Holder<Class>>::get(holder);
|
||||
@ -197,6 +198,78 @@ void construct(value_and_holder &v_h, Alias<Class> &&result, bool) {
|
||||
v_h.value_ptr() = new Alias<Class>(std::move(result));
|
||||
}
|
||||
|
||||
#ifdef PYBIND11_SMART_HOLDER_ENABLED
|
||||
|
||||
template <typename T, typename D>
|
||||
smart_holder init_smart_holder_from_unique_ptr(std::unique_ptr<T, D> &&unq_ptr,
|
||||
bool void_cast_raw_ptr) {
|
||||
void *void_ptr = void_cast_raw_ptr ? static_cast<void *>(unq_ptr.get()) : nullptr;
|
||||
return smart_holder::from_unique_ptr(std::move(unq_ptr), void_ptr);
|
||||
}
|
||||
|
||||
template <typename Class,
|
||||
typename D = std::default_delete<Cpp<Class>>,
|
||||
detail::enable_if_t<is_smart_holder<Holder<Class>>::value, int> = 0>
|
||||
void construct(value_and_holder &v_h, std::unique_ptr<Cpp<Class>, D> &&unq_ptr, bool need_alias) {
|
||||
PYBIND11_WORKAROUND_INCORRECT_MSVC_C4100(need_alias);
|
||||
auto *ptr = unq_ptr.get();
|
||||
no_nullptr(ptr);
|
||||
if (Class::has_alias && need_alias && !is_alias<Class>(ptr)) {
|
||||
throw type_error("pybind11::init(): construction failed: returned std::unique_ptr pointee "
|
||||
"is not an alias instance");
|
||||
}
|
||||
// Here and below: if the new object is a trampoline, the shared_from_this mechanism needs
|
||||
// to be prevented from accessing the smart_holder vptr, because it does not keep the
|
||||
// trampoline Python object alive. For types that don't inherit from enable_shared_from_this
|
||||
// it does not matter if void_cast_raw_ptr is true or false, therefore it's not necessary
|
||||
// to also inspect the type.
|
||||
auto smhldr = init_smart_holder_from_unique_ptr(
|
||||
std::move(unq_ptr), /*void_cast_raw_ptr*/ Class::has_alias && is_alias<Class>(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<is_smart_holder<Holder<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
|
||||
= init_smart_holder_from_unique_ptr(std::move(unq_ptr), /*void_cast_raw_ptr*/ true);
|
||||
v_h.value_ptr() = ptr;
|
||||
v_h.type->init_instance(v_h.inst, &smhldr);
|
||||
}
|
||||
|
||||
template <typename Class, detail::enable_if_t<is_smart_holder<Holder<Class>>::value, int> = 0>
|
||||
void construct(value_and_holder &v_h, std::shared_ptr<Cpp<Class>> &&shd_ptr, bool need_alias) {
|
||||
PYBIND11_WORKAROUND_INCORRECT_MSVC_C4100(need_alias);
|
||||
auto *ptr = shd_ptr.get();
|
||||
no_nullptr(ptr);
|
||||
if (Class::has_alias && need_alias && !is_alias<Class>(ptr)) {
|
||||
throw type_error("pybind11::init(): construction failed: returned std::shared_ptr pointee "
|
||||
"is not an alias instance");
|
||||
}
|
||||
auto smhldr = 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<is_smart_holder<Holder<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 = smart_holder::from_shared_ptr(shd_ptr);
|
||||
v_h.value_ptr() = ptr;
|
||||
v_h.type->init_instance(v_h.inst, &smhldr);
|
||||
}
|
||||
|
||||
#endif // PYBIND11_SMART_HOLDER_ENABLED
|
||||
|
||||
// Implementing class for py::init<...>()
|
||||
template <typename... Args>
|
||||
struct constructor {
|
||||
|
@ -36,7 +36,9 @@
|
||||
/// further ABI-incompatible changes may be made before the ABI is officially
|
||||
/// changed to the new version.
|
||||
#ifndef PYBIND11_INTERNALS_VERSION
|
||||
# if PY_VERSION_HEX >= 0x030C0000 || defined(_MSC_VER)
|
||||
# if PYBIND11_VERSION_MAJOR >= 3
|
||||
# define PYBIND11_INTERNALS_VERSION 6
|
||||
# elif PY_VERSION_HEX >= 0x030C0000 || defined(_MSC_VER)
|
||||
// Version bump for Python 3.12+, before first 3.12 beta release.
|
||||
// Version bump for MSVC piggy-backed on PR #4779. See comments there.
|
||||
# ifdef Py_GIL_DISABLED
|
||||
@ -241,6 +243,25 @@ struct internals {
|
||||
}
|
||||
};
|
||||
|
||||
#if PYBIND11_INTERNALS_VERSION >= 6
|
||||
|
||||
# define PYBIND11_HAS_INTERNALS_WITH_SMART_HOLDER_SUPPORT
|
||||
|
||||
enum class holder_enum_t : uint8_t {
|
||||
undefined,
|
||||
std_unique_ptr, // Default, lacking interop with std::shared_ptr.
|
||||
std_shared_ptr, // Lacking interop with std::unique_ptr.
|
||||
smart_holder, // Full std::unique_ptr / std::shared_ptr interop.
|
||||
custom_holder,
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
#if defined(PYBIND11_HAS_INTERNALS_WITH_SMART_HOLDER_SUPPORT) \
|
||||
&& !defined(PYBIND11_SMART_HOLDER_DISABLE)
|
||||
# define PYBIND11_SMART_HOLDER_ENABLED
|
||||
#endif
|
||||
|
||||
/// Additional type information which does not fit into the PyTypeObject.
|
||||
/// Changes to this struct also require bumping `PYBIND11_INTERNALS_VERSION`.
|
||||
struct type_info {
|
||||
@ -264,9 +285,14 @@ struct type_info {
|
||||
/* True if there is no multiple inheritance in this type's inheritance tree */
|
||||
bool simple_ancestors : 1;
|
||||
/* for base vs derived holder_type checks */
|
||||
// SMART_HOLDER_BAKEIN_FOLLOW_ON: Remove default_holder member here and
|
||||
// produce better error messages in the places where it is currently used.
|
||||
bool default_holder : 1;
|
||||
/* true if this is a type registered with py::module_local */
|
||||
bool module_local : 1;
|
||||
#ifdef PYBIND11_HAS_INTERNALS_WITH_SMART_HOLDER_SUPPORT
|
||||
holder_enum_t holder_enum_v = holder_enum_t::undefined;
|
||||
#endif
|
||||
};
|
||||
|
||||
/// On MSVC, debug and release builds are not ABI-compatible!
|
||||
|
349
include/pybind11/detail/struct_smart_holder.h
Normal file
349
include/pybind11/detail/struct_smart_holder.h
Normal file
@ -0,0 +1,349 @@
|
||||
// Copyright (c) 2020-2024 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 could be extended to included life-time management 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.
|
||||
|
||||
* By choice, the smart_holder is movable but not copyable, to keep the design
|
||||
simple, and to guard against accidental copying overhead.
|
||||
|
||||
* The `void_cast_raw_ptr` option is needed to make the `smart_holder` `vptr`
|
||||
member invisible to the `shared_from_this` mechanism, in case the lifetime
|
||||
of a `PyObject` is tied to the pointee.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <type_traits>
|
||||
#include <typeinfo>
|
||||
#include <utility>
|
||||
|
||||
// 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 {
|
||||
|
||||
static constexpr bool type_has_shared_from_this(...) { return false; }
|
||||
|
||||
template <typename T>
|
||||
static constexpr bool type_has_shared_from_this(const std::enable_shared_from_this<T> *) {
|
||||
return true;
|
||||
}
|
||||
|
||||
struct guarded_delete {
|
||||
std::weak_ptr<void> released_ptr; // Trick to keep the smart_holder memory footprint small.
|
||||
std::function<void(void *)> del_fun; // Rare case.
|
||||
void (*del_ptr)(void *); // Common case.
|
||||
bool use_del_fun;
|
||||
bool armed_flag;
|
||||
guarded_delete(std::function<void(void *)> &&del_fun, bool armed_flag)
|
||||
: del_fun{std::move(del_fun)}, del_ptr{nullptr}, use_del_fun{true},
|
||||
armed_flag{armed_flag} {}
|
||||
guarded_delete(void (*del_ptr)(void *), bool armed_flag)
|
||||
: del_ptr{del_ptr}, use_del_fun{false}, armed_flag{armed_flag} {}
|
||||
void operator()(void *raw_ptr) const {
|
||||
if (armed_flag) {
|
||||
if (use_del_fun) {
|
||||
del_fun(raw_ptr);
|
||||
} else {
|
||||
del_ptr(raw_ptr);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T, typename std::enable_if<std::is_destructible<T>::value, int>::type = 0>
|
||||
inline void builtin_delete_if_destructible(void *raw_ptr) {
|
||||
std::default_delete<T>{}(static_cast<T *>(raw_ptr));
|
||||
}
|
||||
|
||||
template <typename T, typename std::enable_if<!std::is_destructible<T>::value, int>::type = 0>
|
||||
inline void builtin_delete_if_destructible(void *) {
|
||||
// This noop operator is needed to avoid a compilation error (for `delete raw_ptr;`), but
|
||||
// throwing an exception from a destructor will std::terminate the process. Therefore the
|
||||
// runtime check for lifetime-management correctness is implemented elsewhere (in
|
||||
// ensure_pointee_is_destructible()).
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
guarded_delete make_guarded_builtin_delete(bool armed_flag) {
|
||||
return guarded_delete(builtin_delete_if_destructible<T>, armed_flag);
|
||||
}
|
||||
|
||||
template <typename T, typename D>
|
||||
struct custom_deleter {
|
||||
D deleter;
|
||||
explicit custom_deleter(D &&deleter) : deleter{std::forward<D>(deleter)} {}
|
||||
void operator()(void *raw_ptr) { deleter(static_cast<T *>(raw_ptr)); }
|
||||
};
|
||||
|
||||
template <typename T, typename D>
|
||||
guarded_delete make_guarded_custom_deleter(D &&uqp_del, bool armed_flag) {
|
||||
return guarded_delete(
|
||||
std::function<void(void *)>(custom_deleter<T, D>(std::forward<D>(uqp_del))), armed_flag);
|
||||
}
|
||||
|
||||
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 = nullptr;
|
||||
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;
|
||||
bool is_disowned : 1;
|
||||
|
||||
// Design choice: smart_holder is movable but not copyable.
|
||||
smart_holder(smart_holder &&) = default;
|
||||
smart_holder(const smart_holder &) = delete;
|
||||
smart_holder &operator=(smart_holder &&) = delete;
|
||||
smart_holder &operator=(const smart_holder &) = delete;
|
||||
|
||||
smart_holder()
|
||||
: vptr_is_using_noop_deleter{false}, vptr_is_using_builtin_delete{false},
|
||||
vptr_is_external_shared_ptr{false}, is_populated{false}, is_disowned{false} {}
|
||||
|
||||
bool has_pointee() const { return vptr != nullptr; }
|
||||
|
||||
template <typename T>
|
||||
static void ensure_pointee_is_destructible(const char *context) {
|
||||
if (!std::is_destructible<T>::value) {
|
||||
throw std::invalid_argument(std::string("Pointee is not destructible (") + context
|
||||
+ ").");
|
||||
}
|
||||
}
|
||||
|
||||
void ensure_is_populated(const char *context) const {
|
||||
if (!is_populated) {
|
||||
throw std::runtime_error(std::string("Unpopulated holder (") + context + ").");
|
||||
}
|
||||
}
|
||||
void ensure_is_not_disowned(const char *context) const {
|
||||
if (is_disowned) {
|
||||
throw std::runtime_error(std::string("Holder was disowned already (") + context
|
||||
+ ").");
|
||||
}
|
||||
}
|
||||
|
||||
void ensure_vptr_is_using_builtin_delete(const char *context) const {
|
||||
if (vptr_is_external_shared_ptr) {
|
||||
throw std::invalid_argument(std::string("Cannot disown external shared_ptr (")
|
||||
+ context + ").");
|
||||
}
|
||||
if (vptr_is_using_noop_deleter) {
|
||||
throw std::invalid_argument(std::string("Cannot disown non-owning holder (") + context
|
||||
+ ").");
|
||||
}
|
||||
if (!vptr_is_using_builtin_delete) {
|
||||
throw std::invalid_argument(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::invalid_argument(std::string("Missing unique_ptr deleter (") + context
|
||||
+ ").");
|
||||
}
|
||||
ensure_vptr_is_using_builtin_delete(context);
|
||||
} else if (!(*rtti_requested == *rtti_uqp_del)
|
||||
&& !(vptr_is_using_builtin_delete
|
||||
&& is_std_default_delete<T>(*rtti_requested))) {
|
||||
throw std::invalid_argument(std::string("Incompatible unique_ptr deleter (") + context
|
||||
+ ").");
|
||||
}
|
||||
}
|
||||
|
||||
void ensure_has_pointee(const char *context) const {
|
||||
if (!has_pointee()) {
|
||||
throw std::invalid_argument(std::string("Disowned holder (") + context + ").");
|
||||
}
|
||||
}
|
||||
|
||||
void ensure_use_count_1(const char *context) const {
|
||||
if (vptr == nullptr) {
|
||||
throw std::invalid_argument(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.
|
||||
// PYBIND11:REMINDER: This may need to be protected by a mutex in free-threaded Python.
|
||||
if (vptr.use_count() != 1) {
|
||||
throw std::invalid_argument(std::string("Cannot disown use_count != 1 (") + context
|
||||
+ ").");
|
||||
}
|
||||
}
|
||||
|
||||
void reset_vptr_deleter_armed_flag(bool armed_flag) const {
|
||||
auto *vptr_del_ptr = std::get_deleter<guarded_delete>(vptr);
|
||||
if (vptr_del_ptr == nullptr) {
|
||||
throw std::runtime_error(
|
||||
"smart_holder::reset_vptr_deleter_armed_flag() called in an invalid context.");
|
||||
}
|
||||
vptr_del_ptr->armed_flag = armed_flag;
|
||||
}
|
||||
|
||||
// Caller is responsible for precondition: ensure_compatible_rtti_uqp_del<T, D>() must succeed.
|
||||
template <typename T, typename D>
|
||||
std::unique_ptr<D> extract_deleter(const char *context) const {
|
||||
const auto *gd = std::get_deleter<guarded_delete>(vptr);
|
||||
if (gd && gd->use_del_fun) {
|
||||
const auto &custom_deleter_ptr = gd->del_fun.template target<custom_deleter<T, D>>();
|
||||
if (custom_deleter_ptr == nullptr) {
|
||||
throw std::runtime_error(
|
||||
std::string("smart_holder::extract_deleter() precondition failure (") + context
|
||||
+ ").");
|
||||
}
|
||||
static_assert(std::is_copy_constructible<D>::value,
|
||||
"Required for compatibility with smart_holder functionality.");
|
||||
return std::unique_ptr<D>(new D(custom_deleter_ptr->deleter));
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
static smart_holder from_raw_ptr_unowned(void *raw_ptr) {
|
||||
smart_holder hld;
|
||||
hld.vptr.reset(raw_ptr, [](void *) {});
|
||||
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>
|
||||
static smart_holder from_raw_ptr_take_ownership(T *raw_ptr, bool void_cast_raw_ptr = false) {
|
||||
ensure_pointee_is_destructible<T>("from_raw_ptr_take_ownership");
|
||||
smart_holder hld;
|
||||
auto gd = make_guarded_builtin_delete<T>(true);
|
||||
if (void_cast_raw_ptr) {
|
||||
hld.vptr.reset(static_cast<void *>(raw_ptr), std::move(gd));
|
||||
} else {
|
||||
hld.vptr.reset(raw_ptr, std::move(gd));
|
||||
}
|
||||
hld.vptr_is_using_builtin_delete = true;
|
||||
hld.is_populated = true;
|
||||
return hld;
|
||||
}
|
||||
|
||||
// Caller is responsible for ensuring the complex preconditions
|
||||
// (see `smart_holder_type_caster_support::load_helper`).
|
||||
void disown() {
|
||||
reset_vptr_deleter_armed_flag(false);
|
||||
is_disowned = true;
|
||||
}
|
||||
|
||||
// Caller is responsible for ensuring the complex preconditions
|
||||
// (see `smart_holder_type_caster_support::load_helper`).
|
||||
void reclaim_disowned() {
|
||||
reset_vptr_deleter_armed_flag(true);
|
||||
is_disowned = false;
|
||||
}
|
||||
|
||||
// Caller is responsible for ensuring the complex preconditions
|
||||
// (see `smart_holder_type_caster_support::load_helper`).
|
||||
void release_disowned() { vptr.reset(); }
|
||||
|
||||
void ensure_can_release_ownership(const char *context = "ensure_can_release_ownership") const {
|
||||
ensure_is_not_disowned(context);
|
||||
ensure_vptr_is_using_builtin_delete(context);
|
||||
ensure_use_count_1(context);
|
||||
}
|
||||
|
||||
// Caller is responsible for ensuring the complex preconditions
|
||||
// (see `smart_holder_type_caster_support::load_helper`).
|
||||
void release_ownership() {
|
||||
reset_vptr_deleter_armed_flag(false);
|
||||
release_disowned();
|
||||
}
|
||||
|
||||
template <typename T, typename D>
|
||||
static smart_holder from_unique_ptr(std::unique_ptr<T, D> &&unq_ptr,
|
||||
void *void_ptr = nullptr) {
|
||||
smart_holder hld;
|
||||
hld.rtti_uqp_del = &typeid(D);
|
||||
hld.vptr_is_using_builtin_delete = is_std_default_delete<T>(*hld.rtti_uqp_del);
|
||||
guarded_delete gd{nullptr, false};
|
||||
if (hld.vptr_is_using_builtin_delete) {
|
||||
gd = make_guarded_builtin_delete<T>(true);
|
||||
} else {
|
||||
gd = make_guarded_custom_deleter<T, D>(std::move(unq_ptr.get_deleter()), true);
|
||||
}
|
||||
if (void_ptr != nullptr) {
|
||||
hld.vptr.reset(void_ptr, std::move(gd));
|
||||
} else {
|
||||
hld.vptr.reset(unq_ptr.get(), std::move(gd));
|
||||
}
|
||||
(void) unq_ptr.release();
|
||||
hld.is_populated = true;
|
||||
return hld;
|
||||
}
|
||||
|
||||
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
|
@ -9,13 +9,17 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <pybind11/gil.h>
|
||||
#include <pybind11/pytypes.h>
|
||||
#include <pybind11/trampoline_self_life_support.h>
|
||||
|
||||
#include "common.h"
|
||||
#include "cpp_conduit.h"
|
||||
#include "descr.h"
|
||||
#include "dynamic_raw_ptr_cast_if_possible.h"
|
||||
#include "internals.h"
|
||||
#include "typeid.h"
|
||||
#include "using_smart_holder.h"
|
||||
#include "value_and_holder.h"
|
||||
|
||||
#include <cstdint>
|
||||
@ -472,6 +476,365 @@ inline PyThreadState *get_thread_state_unchecked() {
|
||||
void keep_alive_impl(handle nurse, handle patient);
|
||||
inline PyObject *make_new_instance(PyTypeObject *type);
|
||||
|
||||
#ifdef PYBIND11_SMART_HOLDER_ENABLED
|
||||
|
||||
// PYBIND11:REMINDER: Needs refactoring of existing pybind11 code.
|
||||
inline bool deregister_instance(instance *self, void *valptr, const type_info *tinfo);
|
||||
|
||||
PYBIND11_NAMESPACE_BEGIN(smart_holder_type_caster_support)
|
||||
|
||||
struct value_and_holder_helper {
|
||||
value_and_holder loaded_v_h;
|
||||
|
||||
bool have_holder() const {
|
||||
return loaded_v_h.vh != nullptr && loaded_v_h.holder_constructed();
|
||||
}
|
||||
|
||||
smart_holder &holder() const { return loaded_v_h.holder<smart_holder>(); }
|
||||
|
||||
void throw_if_uninitialized_or_disowned_holder(const char *typeid_name) const {
|
||||
static const std::string missing_value_msg = "Missing value for wrapped C++ type `";
|
||||
if (!holder().is_populated) {
|
||||
throw value_error(missing_value_msg + clean_type_id(typeid_name)
|
||||
+ "`: Python instance is uninitialized.");
|
||||
}
|
||||
if (!holder().has_pointee()) {
|
||||
throw value_error(missing_value_msg + clean_type_id(typeid_name)
|
||||
+ "`: Python instance was disowned.");
|
||||
}
|
||||
}
|
||||
|
||||
void throw_if_uninitialized_or_disowned_holder(const std::type_info &type_info) const {
|
||||
throw_if_uninitialized_or_disowned_holder(type_info.name());
|
||||
}
|
||||
|
||||
// have_holder() must be true or this function will fail.
|
||||
void throw_if_instance_is_currently_owned_by_shared_ptr() const {
|
||||
auto *vptr_gd_ptr = std::get_deleter<pybindit::memory::guarded_delete>(holder().vptr);
|
||||
if (vptr_gd_ptr != nullptr && !vptr_gd_ptr->released_ptr.expired()) {
|
||||
throw value_error("Python instance is currently owned by a std::shared_ptr.");
|
||||
}
|
||||
}
|
||||
|
||||
void *get_void_ptr_or_nullptr() const {
|
||||
if (have_holder()) {
|
||||
auto &hld = holder();
|
||||
if (hld.is_populated && hld.has_pointee()) {
|
||||
return hld.template as_raw_ptr_unowned<void>();
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T, typename D>
|
||||
handle smart_holder_from_unique_ptr(std::unique_ptr<T, D> &&src,
|
||||
return_value_policy policy,
|
||||
handle parent,
|
||||
const std::pair<const void *, const type_info *> &st) {
|
||||
if (policy == return_value_policy::copy) {
|
||||
throw cast_error("return_value_policy::copy is invalid for unique_ptr.");
|
||||
}
|
||||
if (!src) {
|
||||
return none().release();
|
||||
}
|
||||
void *src_raw_void_ptr = const_cast<void *>(st.first);
|
||||
assert(st.second != nullptr);
|
||||
const detail::type_info *tinfo = st.second;
|
||||
if (handle existing_inst = find_registered_python_instance(src_raw_void_ptr, tinfo)) {
|
||||
auto *self_life_support
|
||||
= dynamic_raw_ptr_cast_if_possible<trampoline_self_life_support>(src.get());
|
||||
if (self_life_support != nullptr) {
|
||||
value_and_holder &v_h = self_life_support->v_h;
|
||||
if (v_h.inst != nullptr && v_h.vh != nullptr) {
|
||||
auto &holder = v_h.holder<smart_holder>();
|
||||
if (!holder.is_disowned) {
|
||||
pybind11_fail("smart_holder_from_unique_ptr: unexpected "
|
||||
"smart_holder.is_disowned failure.");
|
||||
}
|
||||
// Critical transfer-of-ownership section. This must stay together.
|
||||
self_life_support->deactivate_life_support();
|
||||
holder.reclaim_disowned();
|
||||
(void) src.release();
|
||||
// Critical section end.
|
||||
return existing_inst;
|
||||
}
|
||||
}
|
||||
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;
|
||||
|
||||
if (static_cast<void *>(src.get()) == src_raw_void_ptr) {
|
||||
// This is a multiple-inheritance situation that is incompatible with the current
|
||||
// shared_from_this handling (see PR #3023). Is there a better solution?
|
||||
src_raw_void_ptr = nullptr;
|
||||
}
|
||||
auto smhldr = smart_holder::from_unique_ptr(std::move(src), src_raw_void_ptr);
|
||||
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 T, typename D>
|
||||
handle smart_holder_from_unique_ptr(std::unique_ptr<T const, D> &&src,
|
||||
return_value_policy policy,
|
||||
handle parent,
|
||||
const std::pair<const void *, const type_info *> &st) {
|
||||
return smart_holder_from_unique_ptr(
|
||||
std::unique_ptr<T, D>(const_cast<T *>(src.release()),
|
||||
std::move(src.get_deleter())), // Const2Mutbl
|
||||
policy,
|
||||
parent,
|
||||
st);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
handle smart_holder_from_shared_ptr(const std::shared_ptr<T> &src,
|
||||
return_value_policy policy,
|
||||
handle parent,
|
||||
const std::pair<const void *, const type_info *> &st) {
|
||||
switch (policy) {
|
||||
case return_value_policy::automatic:
|
||||
case return_value_policy::automatic_reference:
|
||||
break;
|
||||
case return_value_policy::take_ownership:
|
||||
throw cast_error("Invalid return_value_policy for shared_ptr (take_ownership).");
|
||||
case return_value_policy::copy:
|
||||
case return_value_policy::move:
|
||||
break;
|
||||
case return_value_policy::reference:
|
||||
throw cast_error("Invalid return_value_policy for shared_ptr (reference).");
|
||||
case return_value_policy::reference_internal:
|
||||
break;
|
||||
}
|
||||
if (!src) {
|
||||
return none().release();
|
||||
}
|
||||
|
||||
auto src_raw_ptr = src.get();
|
||||
assert(st.second != nullptr);
|
||||
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)) {
|
||||
// PYBIND11:REMINDER: MISSING: Enforcement of consistency with existing smart_holder.
|
||||
// PYBIND11:REMINDER: 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
|
||||
= smart_holder::from_shared_ptr(std::shared_ptr<void>(src, const_cast<void *>(st.first)));
|
||||
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 T>
|
||||
handle smart_holder_from_shared_ptr(const std::shared_ptr<T const> &src,
|
||||
return_value_policy policy,
|
||||
handle parent,
|
||||
const std::pair<const void *, const type_info *> &st) {
|
||||
return smart_holder_from_shared_ptr(std::const_pointer_cast<T>(src), // Const2Mutbl
|
||||
policy,
|
||||
parent,
|
||||
st);
|
||||
}
|
||||
|
||||
struct shared_ptr_parent_life_support {
|
||||
PyObject *parent;
|
||||
explicit shared_ptr_parent_life_support(PyObject *parent) : parent{parent} {
|
||||
Py_INCREF(parent);
|
||||
}
|
||||
// NOLINTNEXTLINE(readability-make-member-function-const)
|
||||
void operator()(void *) {
|
||||
gil_scoped_acquire gil;
|
||||
Py_DECREF(parent);
|
||||
}
|
||||
};
|
||||
|
||||
struct shared_ptr_trampoline_self_life_support {
|
||||
PyObject *self;
|
||||
explicit shared_ptr_trampoline_self_life_support(instance *inst)
|
||||
: self{reinterpret_cast<PyObject *>(inst)} {
|
||||
gil_scoped_acquire gil;
|
||||
Py_INCREF(self);
|
||||
}
|
||||
// NOLINTNEXTLINE(readability-make-member-function-const)
|
||||
void operator()(void *) {
|
||||
gil_scoped_acquire gil;
|
||||
Py_DECREF(self);
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T,
|
||||
typename D,
|
||||
typename std::enable_if<std::is_default_constructible<D>::value, int>::type = 0>
|
||||
inline std::unique_ptr<T, D> unique_with_deleter(T *raw_ptr, std::unique_ptr<D> &&deleter) {
|
||||
if (deleter == nullptr) {
|
||||
return std::unique_ptr<T, D>(raw_ptr);
|
||||
}
|
||||
return std::unique_ptr<T, D>(raw_ptr, std::move(*deleter));
|
||||
}
|
||||
|
||||
template <typename T,
|
||||
typename D,
|
||||
typename std::enable_if<!std::is_default_constructible<D>::value, int>::type = 0>
|
||||
inline std::unique_ptr<T, D> unique_with_deleter(T *raw_ptr, std::unique_ptr<D> &&deleter) {
|
||||
if (deleter == nullptr) {
|
||||
pybind11_fail("smart_holder_type_casters: deleter is not default constructible and no"
|
||||
" instance available to return.");
|
||||
}
|
||||
return std::unique_ptr<T, D>(raw_ptr, std::move(*deleter));
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
struct load_helper : value_and_holder_helper {
|
||||
bool was_populated = false;
|
||||
bool python_instance_is_alias = false;
|
||||
|
||||
void maybe_set_python_instance_is_alias(handle src) {
|
||||
if (was_populated) {
|
||||
python_instance_is_alias = reinterpret_cast<instance *>(src.ptr())->is_alias;
|
||||
}
|
||||
}
|
||||
|
||||
static std::shared_ptr<T> make_shared_ptr_with_responsible_parent(T *raw_ptr, handle parent) {
|
||||
return std::shared_ptr<T>(raw_ptr, shared_ptr_parent_life_support(parent.ptr()));
|
||||
}
|
||||
|
||||
std::shared_ptr<T> load_as_shared_ptr(void *void_raw_ptr,
|
||||
handle responsible_parent = nullptr) const {
|
||||
if (!have_holder()) {
|
||||
return nullptr;
|
||||
}
|
||||
throw_if_uninitialized_or_disowned_holder(typeid(T));
|
||||
smart_holder &hld = holder();
|
||||
hld.ensure_is_not_disowned("load_as_shared_ptr");
|
||||
if (hld.vptr_is_using_noop_deleter) {
|
||||
if (responsible_parent) {
|
||||
return make_shared_ptr_with_responsible_parent(static_cast<T *>(void_raw_ptr),
|
||||
responsible_parent);
|
||||
}
|
||||
throw std::runtime_error("Non-owning holder (load_as_shared_ptr).");
|
||||
}
|
||||
auto *type_raw_ptr = static_cast<T *>(void_raw_ptr);
|
||||
if (python_instance_is_alias) {
|
||||
auto *vptr_gd_ptr = std::get_deleter<pybindit::memory::guarded_delete>(hld.vptr);
|
||||
if (vptr_gd_ptr != nullptr) {
|
||||
std::shared_ptr<void> released_ptr = vptr_gd_ptr->released_ptr.lock();
|
||||
if (released_ptr) {
|
||||
return std::shared_ptr<T>(released_ptr, type_raw_ptr);
|
||||
}
|
||||
std::shared_ptr<T> to_be_released(
|
||||
type_raw_ptr, shared_ptr_trampoline_self_life_support(loaded_v_h.inst));
|
||||
vptr_gd_ptr->released_ptr = to_be_released;
|
||||
return to_be_released;
|
||||
}
|
||||
auto *sptsls_ptr = std::get_deleter<shared_ptr_trampoline_self_life_support>(hld.vptr);
|
||||
if (sptsls_ptr != nullptr) {
|
||||
// This code is reachable only if there are multiple registered_instances for the
|
||||
// same pointee.
|
||||
if (reinterpret_cast<PyObject *>(loaded_v_h.inst) == sptsls_ptr->self) {
|
||||
pybind11_fail("smart_holder_type_caster_support load_as_shared_ptr failure: "
|
||||
"loaded_v_h.inst == sptsls_ptr->self");
|
||||
}
|
||||
}
|
||||
if (sptsls_ptr != nullptr
|
||||
|| !pybindit::memory::type_has_shared_from_this(type_raw_ptr)) {
|
||||
return std::shared_ptr<T>(
|
||||
type_raw_ptr, shared_ptr_trampoline_self_life_support(loaded_v_h.inst));
|
||||
}
|
||||
if (hld.vptr_is_external_shared_ptr) {
|
||||
pybind11_fail("smart_holder_type_casters load_as_shared_ptr failure: not "
|
||||
"implemented: trampoline-self-life-support for external shared_ptr "
|
||||
"to type inheriting from std::enable_shared_from_this.");
|
||||
}
|
||||
pybind11_fail(
|
||||
"smart_holder_type_casters: load_as_shared_ptr failure: internal inconsistency.");
|
||||
}
|
||||
std::shared_ptr<void> void_shd_ptr = hld.template as_shared_ptr<void>();
|
||||
return std::shared_ptr<T>(void_shd_ptr, type_raw_ptr);
|
||||
}
|
||||
|
||||
template <typename D>
|
||||
std::unique_ptr<T, D> load_as_unique_ptr(void *raw_void_ptr,
|
||||
const char *context = "load_as_unique_ptr") {
|
||||
if (!have_holder()) {
|
||||
return unique_with_deleter<T, D>(nullptr, std::unique_ptr<D>());
|
||||
}
|
||||
throw_if_uninitialized_or_disowned_holder(typeid(T));
|
||||
throw_if_instance_is_currently_owned_by_shared_ptr();
|
||||
holder().ensure_is_not_disowned(context);
|
||||
holder().template ensure_compatible_rtti_uqp_del<T, D>(context);
|
||||
holder().ensure_use_count_1(context);
|
||||
|
||||
T *raw_type_ptr = static_cast<T *>(raw_void_ptr);
|
||||
|
||||
auto *self_life_support
|
||||
= dynamic_raw_ptr_cast_if_possible<trampoline_self_life_support>(raw_type_ptr);
|
||||
if (self_life_support == nullptr && python_instance_is_alias) {
|
||||
throw value_error("Alias class (also known as trampoline) does not inherit from "
|
||||
"py::trampoline_self_life_support, therefore the ownership of this "
|
||||
"instance cannot safely be transferred to C++.");
|
||||
}
|
||||
|
||||
std::unique_ptr<D> extracted_deleter = holder().template extract_deleter<T, D>(context);
|
||||
|
||||
// Critical transfer-of-ownership section. This must stay together.
|
||||
if (self_life_support != nullptr) {
|
||||
holder().disown();
|
||||
} else {
|
||||
holder().release_ownership();
|
||||
}
|
||||
auto result = unique_with_deleter<T, D>(raw_type_ptr, std::move(extracted_deleter));
|
||||
if (self_life_support != nullptr) {
|
||||
self_life_support->activate_life_support(loaded_v_h);
|
||||
} else {
|
||||
void *value_void_ptr = loaded_v_h.value_ptr();
|
||||
loaded_v_h.value_ptr() = nullptr;
|
||||
deregister_instance(loaded_v_h.inst, value_void_ptr, loaded_v_h.type);
|
||||
}
|
||||
// Critical section end.
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// This assumes load_as_shared_ptr succeeded(), and the returned shared_ptr is still alive.
|
||||
// The returned unique_ptr is meant to never expire (the behavior is undefined otherwise).
|
||||
template <typename D>
|
||||
std::unique_ptr<T, D>
|
||||
load_as_const_unique_ptr(T *raw_type_ptr, const char *context = "load_as_const_unique_ptr") {
|
||||
if (!have_holder()) {
|
||||
return unique_with_deleter<T, D>(nullptr, std::unique_ptr<D>());
|
||||
}
|
||||
holder().template ensure_compatible_rtti_uqp_del<T, D>(context);
|
||||
return unique_with_deleter<T, D>(
|
||||
raw_type_ptr, std::move(holder().template extract_deleter<T, D>(context)));
|
||||
}
|
||||
};
|
||||
|
||||
PYBIND11_NAMESPACE_END(smart_holder_type_caster_support)
|
||||
|
||||
#endif // PYBIND11_SMART_HOLDER_ENABLED
|
||||
|
||||
class type_caster_generic {
|
||||
public:
|
||||
PYBIND11_NOINLINE explicit type_caster_generic(const std::type_info &type_info)
|
||||
@ -576,6 +939,17 @@ public:
|
||||
|
||||
// Base methods for generic caster; there are overridden in copyable_holder_caster
|
||||
void load_value(value_and_holder &&v_h) {
|
||||
#ifdef PYBIND11_SMART_HOLDER_ENABLED
|
||||
if (typeinfo->holder_enum_v == detail::holder_enum_t::smart_holder) {
|
||||
smart_holder_type_caster_support::value_and_holder_helper v_h_helper;
|
||||
v_h_helper.loaded_v_h = v_h;
|
||||
if (v_h_helper.have_holder()) {
|
||||
v_h_helper.throw_if_uninitialized_or_disowned_holder(cpptype->name());
|
||||
value = v_h_helper.holder().template as_raw_ptr_unowned<void>();
|
||||
return;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
auto *&vptr = v_h.value_ptr();
|
||||
// Lazy allocation for unallocated values:
|
||||
if (vptr == nullptr) {
|
||||
|
33
include/pybind11/detail/using_smart_holder.h
Normal file
33
include/pybind11/detail/using_smart_holder.h
Normal file
@ -0,0 +1,33 @@
|
||||
// Copyright (c) 2024 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 "internals.h"
|
||||
|
||||
#include <type_traits>
|
||||
|
||||
#ifdef PYBIND11_SMART_HOLDER_ENABLED
|
||||
# include "struct_smart_holder.h"
|
||||
#endif
|
||||
|
||||
PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE)
|
||||
|
||||
#ifdef PYBIND11_SMART_HOLDER_ENABLED
|
||||
using pybindit::memory::smart_holder;
|
||||
#endif
|
||||
|
||||
PYBIND11_NAMESPACE_BEGIN(detail)
|
||||
|
||||
#ifdef PYBIND11_SMART_HOLDER_ENABLED
|
||||
template <typename H>
|
||||
using is_smart_holder = std::is_same<H, smart_holder>;
|
||||
#else
|
||||
template <typename>
|
||||
struct is_smart_holder : std::false_type {};
|
||||
#endif
|
||||
|
||||
PYBIND11_NAMESPACE_END(detail)
|
||||
PYBIND11_NAMESPACE_END(PYBIND11_NAMESPACE)
|
@ -10,8 +10,10 @@
|
||||
|
||||
#pragma once
|
||||
#include "detail/class.h"
|
||||
#include "detail/dynamic_raw_ptr_cast_if_possible.h"
|
||||
#include "detail/exception_translation.h"
|
||||
#include "detail/init.h"
|
||||
#include "detail/using_smart_holder.h"
|
||||
#include "attr.h"
|
||||
#include "gil.h"
|
||||
#include "gil_safe_call_once.h"
|
||||
@ -1373,6 +1375,9 @@ protected:
|
||||
tinfo->simple_ancestors = true;
|
||||
tinfo->default_holder = rec.default_holder;
|
||||
tinfo->module_local = rec.module_local;
|
||||
#ifdef PYBIND11_HAS_INTERNALS_WITH_SMART_HOLDER_SUPPORT
|
||||
tinfo->holder_enum_v = rec.holder_enum_v;
|
||||
#endif
|
||||
|
||||
with_internals([&](internals &internals) {
|
||||
auto tindex = std::type_index(*rec.type);
|
||||
@ -1545,6 +1550,245 @@ auto method_adaptor(Return (Class::*pmf)(Args...) const) -> Return (Derived::*)(
|
||||
return pmf;
|
||||
}
|
||||
|
||||
PYBIND11_NAMESPACE_BEGIN(detail)
|
||||
|
||||
// Helper for the property_cpp_function static member functions below.
|
||||
// The only purpose of these functions is to support .def_readonly & .def_readwrite.
|
||||
// In this context, the PM template parameter is certain to be a Pointer to a Member.
|
||||
// The main purpose of must_be_member_function_pointer is to make this obvious, and to guard
|
||||
// against accidents. As a side-effect, it also explains why the syntactical overhead for
|
||||
// perfect forwarding is not needed.
|
||||
template <typename PM>
|
||||
using must_be_member_function_pointer = enable_if_t<std::is_member_pointer<PM>::value, int>;
|
||||
|
||||
// Note that property_cpp_function is intentionally in the main pybind11 namespace,
|
||||
// because user-defined specializations could be useful.
|
||||
|
||||
// Classic (non-smart_holder) implementations for .def_readonly and .def_readwrite
|
||||
// getter and setter functions.
|
||||
// WARNING: This classic implementation can lead to dangling pointers for raw pointer members.
|
||||
// See test_ptr() in tests/test_class_sh_property.py
|
||||
// However, this implementation works as-is (and safely) for smart_holder std::shared_ptr members.
|
||||
template <typename T, typename D>
|
||||
struct property_cpp_function_classic {
|
||||
template <typename PM, must_be_member_function_pointer<PM> = 0>
|
||||
static cpp_function readonly(PM pm, const handle &hdl) {
|
||||
return cpp_function([pm](const T &c) -> const D & { return c.*pm; }, is_method(hdl));
|
||||
}
|
||||
|
||||
template <typename PM, must_be_member_function_pointer<PM> = 0>
|
||||
static cpp_function read(PM pm, const handle &hdl) {
|
||||
return readonly(pm, hdl);
|
||||
}
|
||||
|
||||
template <typename PM, must_be_member_function_pointer<PM> = 0>
|
||||
static cpp_function write(PM pm, const handle &hdl) {
|
||||
return cpp_function([pm](T &c, const D &value) { c.*pm = value; }, is_method(hdl));
|
||||
}
|
||||
};
|
||||
|
||||
PYBIND11_NAMESPACE_END(detail)
|
||||
|
||||
template <typename T, typename D, typename SFINAE = void>
|
||||
struct property_cpp_function : detail::property_cpp_function_classic<T, D> {};
|
||||
|
||||
#ifdef PYBIND11_SMART_HOLDER_ENABLED
|
||||
|
||||
PYBIND11_NAMESPACE_BEGIN(detail)
|
||||
|
||||
template <typename T, typename D, typename SFINAE = void>
|
||||
struct both_t_and_d_use_type_caster_base : std::false_type {};
|
||||
|
||||
// `T` is assumed to be equivalent to `intrinsic_t<T>`.
|
||||
// `D` is may or may not be equivalent to `intrinsic_t<D>`.
|
||||
template <typename T, typename D>
|
||||
struct both_t_and_d_use_type_caster_base<
|
||||
T,
|
||||
D,
|
||||
enable_if_t<all_of<std::is_base_of<type_caster_base<T>, type_caster<T>>,
|
||||
std::is_base_of<type_caster_base<intrinsic_t<D>>, make_caster<D>>>::value>>
|
||||
: std::true_type {};
|
||||
|
||||
// Specialization for raw pointer members, using smart_holder if that is the class_ holder,
|
||||
// or falling back to the classic implementation if not.
|
||||
// WARNING: Like the classic implementation, this implementation can lead to dangling pointers.
|
||||
// See test_ptr() in tests/test_class_sh_property.py
|
||||
// However, the read functions return a shared_ptr to the member, emulating the PyCLIF approach:
|
||||
// https://github.com/google/clif/blob/c371a6d4b28d25d53a16e6d2a6d97305fb1be25a/clif/python/instance.h#L233
|
||||
// This prevents disowning of the Python object owning the raw pointer member.
|
||||
template <typename T, typename D>
|
||||
struct property_cpp_function_sh_raw_ptr_member {
|
||||
using drp = typename std::remove_pointer<D>::type;
|
||||
|
||||
template <typename PM, must_be_member_function_pointer<PM> = 0>
|
||||
static cpp_function readonly(PM pm, const handle &hdl) {
|
||||
type_info *tinfo = get_type_info(typeid(T), /*throw_if_missing=*/true);
|
||||
if (tinfo->holder_enum_v == holder_enum_t::smart_holder) {
|
||||
return cpp_function(
|
||||
[pm](handle c_hdl) -> std::shared_ptr<drp> {
|
||||
std::shared_ptr<T> c_sp
|
||||
= type_caster<std::shared_ptr<T>>::shared_ptr_with_responsible_parent(
|
||||
c_hdl);
|
||||
D ptr = (*c_sp).*pm;
|
||||
return std::shared_ptr<drp>(c_sp, ptr);
|
||||
},
|
||||
is_method(hdl));
|
||||
}
|
||||
return property_cpp_function_classic<T, D>::readonly(pm, hdl);
|
||||
}
|
||||
|
||||
template <typename PM, must_be_member_function_pointer<PM> = 0>
|
||||
static cpp_function read(PM pm, const handle &hdl) {
|
||||
return readonly(pm, hdl);
|
||||
}
|
||||
|
||||
template <typename PM, must_be_member_function_pointer<PM> = 0>
|
||||
static cpp_function write(PM pm, const handle &hdl) {
|
||||
type_info *tinfo = get_type_info(typeid(T), /*throw_if_missing=*/true);
|
||||
if (tinfo->holder_enum_v == holder_enum_t::smart_holder) {
|
||||
return cpp_function([pm](T &c, D value) { c.*pm = std::forward<D>(std::move(value)); },
|
||||
is_method(hdl));
|
||||
}
|
||||
return property_cpp_function_classic<T, D>::write(pm, hdl);
|
||||
}
|
||||
};
|
||||
|
||||
// Specialization for members held by-value, using smart_holder if that is the class_ holder,
|
||||
// or falling back to the classic implementation if not.
|
||||
// The read functions return a shared_ptr to the member, emulating the PyCLIF approach:
|
||||
// https://github.com/google/clif/blob/c371a6d4b28d25d53a16e6d2a6d97305fb1be25a/clif/python/instance.h#L233
|
||||
// This prevents disowning of the Python object owning the member.
|
||||
template <typename T, typename D>
|
||||
struct property_cpp_function_sh_member_held_by_value {
|
||||
template <typename PM, must_be_member_function_pointer<PM> = 0>
|
||||
static cpp_function readonly(PM pm, const handle &hdl) {
|
||||
type_info *tinfo = get_type_info(typeid(T), /*throw_if_missing=*/true);
|
||||
if (tinfo->holder_enum_v == holder_enum_t::smart_holder) {
|
||||
return cpp_function(
|
||||
[pm](handle c_hdl) -> std::shared_ptr<typename std::add_const<D>::type> {
|
||||
std::shared_ptr<T> c_sp
|
||||
= type_caster<std::shared_ptr<T>>::shared_ptr_with_responsible_parent(
|
||||
c_hdl);
|
||||
return std::shared_ptr<typename std::add_const<D>::type>(c_sp,
|
||||
&(c_sp.get()->*pm));
|
||||
},
|
||||
is_method(hdl));
|
||||
}
|
||||
return property_cpp_function_classic<T, D>::readonly(pm, hdl);
|
||||
}
|
||||
|
||||
template <typename PM, must_be_member_function_pointer<PM> = 0>
|
||||
static cpp_function read(PM pm, const handle &hdl) {
|
||||
type_info *tinfo = get_type_info(typeid(T), /*throw_if_missing=*/true);
|
||||
if (tinfo->holder_enum_v == holder_enum_t::smart_holder) {
|
||||
return cpp_function(
|
||||
[pm](handle c_hdl) -> std::shared_ptr<D> {
|
||||
std::shared_ptr<T> c_sp
|
||||
= type_caster<std::shared_ptr<T>>::shared_ptr_with_responsible_parent(
|
||||
c_hdl);
|
||||
return std::shared_ptr<D>(c_sp, &(c_sp.get()->*pm));
|
||||
},
|
||||
is_method(hdl));
|
||||
}
|
||||
return property_cpp_function_classic<T, D>::read(pm, hdl);
|
||||
}
|
||||
|
||||
template <typename PM, must_be_member_function_pointer<PM> = 0>
|
||||
static cpp_function write(PM pm, const handle &hdl) {
|
||||
type_info *tinfo = get_type_info(typeid(T), /*throw_if_missing=*/true);
|
||||
if (tinfo->holder_enum_v == holder_enum_t::smart_holder) {
|
||||
return cpp_function([pm](T &c, const D &value) { c.*pm = value; }, is_method(hdl));
|
||||
}
|
||||
return property_cpp_function_classic<T, D>::write(pm, hdl);
|
||||
}
|
||||
};
|
||||
|
||||
// Specialization for std::unique_ptr members, using smart_holder if that is the class_ holder,
|
||||
// or falling back to the classic implementation if not.
|
||||
// read disowns the member unique_ptr.
|
||||
// write disowns the passed Python object.
|
||||
// readonly is disabled (static_assert) because there is no safe & intuitive way to make the member
|
||||
// accessible as a Python object without disowning the member unique_ptr. A .def_readonly disowning
|
||||
// the unique_ptr member is deemed highly prone to misunderstandings.
|
||||
template <typename T, typename D>
|
||||
struct property_cpp_function_sh_unique_ptr_member {
|
||||
template <typename PM, must_be_member_function_pointer<PM> = 0>
|
||||
static cpp_function readonly(PM, const handle &) {
|
||||
static_assert(!is_instantiation<std::unique_ptr, D>::value,
|
||||
"def_readonly cannot be used for std::unique_ptr members.");
|
||||
return cpp_function{}; // Unreachable.
|
||||
}
|
||||
|
||||
template <typename PM, must_be_member_function_pointer<PM> = 0>
|
||||
static cpp_function read(PM pm, const handle &hdl) {
|
||||
type_info *tinfo = get_type_info(typeid(T), /*throw_if_missing=*/true);
|
||||
if (tinfo->holder_enum_v == holder_enum_t::smart_holder) {
|
||||
return cpp_function(
|
||||
[pm](handle c_hdl) -> D {
|
||||
std::shared_ptr<T> c_sp
|
||||
= type_caster<std::shared_ptr<T>>::shared_ptr_with_responsible_parent(
|
||||
c_hdl);
|
||||
return D{std::move(c_sp.get()->*pm)};
|
||||
},
|
||||
is_method(hdl));
|
||||
}
|
||||
return property_cpp_function_classic<T, D>::read(pm, hdl);
|
||||
}
|
||||
|
||||
template <typename PM, must_be_member_function_pointer<PM> = 0>
|
||||
static cpp_function write(PM pm, const handle &hdl) {
|
||||
return cpp_function([pm](T &c, D &&value) { c.*pm = std::move(value); }, is_method(hdl));
|
||||
}
|
||||
};
|
||||
|
||||
PYBIND11_NAMESPACE_END(detail)
|
||||
|
||||
template <typename T, typename D>
|
||||
struct property_cpp_function<
|
||||
T,
|
||||
D,
|
||||
detail::enable_if_t<detail::all_of<std::is_pointer<D>,
|
||||
detail::both_t_and_d_use_type_caster_base<T, D>>::value>>
|
||||
: detail::property_cpp_function_sh_raw_ptr_member<T, D> {};
|
||||
|
||||
template <typename T, typename D>
|
||||
struct property_cpp_function<T,
|
||||
D,
|
||||
detail::enable_if_t<detail::all_of<
|
||||
detail::none_of<std::is_pointer<D>,
|
||||
std::is_array<D>,
|
||||
detail::is_instantiation<std::unique_ptr, D>,
|
||||
detail::is_instantiation<std::shared_ptr, D>>,
|
||||
detail::both_t_and_d_use_type_caster_base<T, D>>::value>>
|
||||
: detail::property_cpp_function_sh_member_held_by_value<T, D> {};
|
||||
|
||||
template <typename T, typename D>
|
||||
struct property_cpp_function<
|
||||
T,
|
||||
D,
|
||||
detail::enable_if_t<detail::all_of<
|
||||
detail::is_instantiation<std::unique_ptr, D>,
|
||||
detail::both_t_and_d_use_type_caster_base<T, typename D::element_type>>::value>>
|
||||
: detail::property_cpp_function_sh_unique_ptr_member<T, D> {};
|
||||
|
||||
#endif // PYBIND11_SMART_HOLDER_ENABLED
|
||||
|
||||
#if defined(PYBIND11_USE_SMART_HOLDER_AS_DEFAULT) && defined(PYBIND11_SMART_HOLDER_ENABLED)
|
||||
// NOTE: THIS IS MEANT FOR STRESS-TESTING ONLY!
|
||||
// As of PR #5257, for production use, there is no longer a strong reason to make
|
||||
// smart_holder the default holder:
|
||||
// Simply use `py::classh` (see below) instead of `py::class_` as needed.
|
||||
// Running the pybind11 unit tests with smart_holder as the default holder is to ensure
|
||||
// that `py::smart_holder` / `py::classh` is backward-compatible with all pre-existing
|
||||
// functionality.
|
||||
# define PYBIND11_ACTUALLY_USING_SMART_HOLDER_AS_DEFAULT
|
||||
template <typename>
|
||||
using default_holder_type = smart_holder;
|
||||
#else
|
||||
template <typename T>
|
||||
using default_holder_type = std::unique_ptr<T>;
|
||||
#endif
|
||||
|
||||
template <typename type_, typename... options>
|
||||
class class_ : public detail::generic_type {
|
||||
template <typename T>
|
||||
@ -1561,7 +1805,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");
|
||||
@ -1593,8 +1837,22 @@ 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;
|
||||
|
||||
#ifdef PYBIND11_SMART_HOLDER_ENABLED
|
||||
if (detail::is_instantiation<std::unique_ptr, holder_type>::value) {
|
||||
record.holder_enum_v = detail::holder_enum_t::std_unique_ptr;
|
||||
} else if (detail::is_instantiation<std::shared_ptr, holder_type>::value) {
|
||||
record.holder_enum_v = detail::holder_enum_t::std_shared_ptr;
|
||||
} else if (std::is_same<holder_type, smart_holder>::value) {
|
||||
record.holder_enum_v = detail::holder_enum_t::smart_holder;
|
||||
} else {
|
||||
record.holder_enum_v = detail::holder_enum_t::custom_holder;
|
||||
}
|
||||
#endif
|
||||
|
||||
set_operator_new<type>(&record);
|
||||
|
||||
/* Register base classes specified via template arguments to class_, if any */
|
||||
@ -1726,9 +1984,11 @@ public:
|
||||
class_ &def_readwrite(const char *name, D C::*pm, const Extra &...extra) {
|
||||
static_assert(std::is_same<C, type>::value || std::is_base_of<C, type>::value,
|
||||
"def_readwrite() requires a class member (or base class member)");
|
||||
cpp_function fget([pm](const type &c) -> const D & { return c.*pm; }, is_method(*this)),
|
||||
fset([pm](type &c, const D &value) { c.*pm = value; }, is_method(*this));
|
||||
def_property(name, fget, fset, return_value_policy::reference_internal, extra...);
|
||||
def_property(name,
|
||||
property_cpp_function<type, D>::read(pm, *this),
|
||||
property_cpp_function<type, D>::write(pm, *this),
|
||||
return_value_policy::reference_internal,
|
||||
extra...);
|
||||
return *this;
|
||||
}
|
||||
|
||||
@ -1736,8 +1996,10 @@ public:
|
||||
class_ &def_readonly(const char *name, const D C::*pm, const Extra &...extra) {
|
||||
static_assert(std::is_same<C, type>::value || std::is_base_of<C, type>::value,
|
||||
"def_readonly() requires a class member (or base class member)");
|
||||
cpp_function fget([pm](const type &c) -> const D & { return c.*pm; }, is_method(*this));
|
||||
def_property_readonly(name, fget, return_value_policy::reference_internal, extra...);
|
||||
def_property_readonly(name,
|
||||
property_cpp_function<type, D>::readonly(pm, *this),
|
||||
return_value_policy::reference_internal,
|
||||
extra...);
|
||||
return *this;
|
||||
}
|
||||
|
||||
@ -1914,6 +2176,8 @@ 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 H = holder_type,
|
||||
detail::enable_if_t<!detail::is_smart_holder<H>::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()) {
|
||||
@ -1923,6 +2187,69 @@ private:
|
||||
init_holder(inst, v_h, (const holder_type *) holder_ptr, v_h.value_ptr<type>());
|
||||
}
|
||||
|
||||
#ifdef PYBIND11_SMART_HOLDER_ENABLED
|
||||
|
||||
template <typename WrappedType>
|
||||
static bool try_initialization_using_shared_from_this(holder_type *, WrappedType *, ...) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Adopting existing approach used by type_caster_base, although it leads to somewhat fuzzy
|
||||
// ownership semantics: if we detected via shared_from_this that a shared_ptr exists already,
|
||||
// it is reused, irrespective of the return_value_policy in effect.
|
||||
// "SomeBaseOfWrappedType" is needed because std::enable_shared_from_this is not necessarily a
|
||||
// direct base of WrappedType.
|
||||
template <typename WrappedType, typename SomeBaseOfWrappedType>
|
||||
static bool try_initialization_using_shared_from_this(
|
||||
holder_type *uninitialized_location,
|
||||
WrappedType *value_ptr_w_t,
|
||||
const std::enable_shared_from_this<SomeBaseOfWrappedType> *) {
|
||||
auto shd_ptr = std::dynamic_pointer_cast<WrappedType>(
|
||||
detail::try_get_shared_from_this(value_ptr_w_t));
|
||||
if (!shd_ptr) {
|
||||
return false;
|
||||
}
|
||||
// Note: inst->owned ignored.
|
||||
new (uninitialized_location) holder_type(holder_type::from_shared_ptr(shd_ptr));
|
||||
return true;
|
||||
}
|
||||
|
||||
template <typename H = holder_type,
|
||||
detail::enable_if_t<detail::is_smart_holder<H>::value, int> = 0>
|
||||
static void init_instance(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(type)));
|
||||
if (!v_h.instance_registered()) {
|
||||
register_instance(inst, v_h.value_ptr(), v_h.type);
|
||||
v_h.set_instance_registered();
|
||||
}
|
||||
auto *uninitialized_location = std::addressof(v_h.holder<holder_type>());
|
||||
auto *value_ptr_w_t = v_h.value_ptr<type>();
|
||||
// Try downcast from `type` to `type_alias`:
|
||||
inst->is_alias
|
||||
= detail::dynamic_raw_ptr_cast_if_possible<type_alias>(value_ptr_w_t) != nullptr;
|
||||
if (holder_void_ptr) {
|
||||
// Note: inst->owned ignored.
|
||||
auto *holder_ptr = static_cast<holder_type *>(holder_void_ptr);
|
||||
new (uninitialized_location) holder_type(std::move(*holder_ptr));
|
||||
} else if (!try_initialization_using_shared_from_this(
|
||||
uninitialized_location, value_ptr_w_t, value_ptr_w_t)) {
|
||||
if (inst->owned) {
|
||||
new (uninitialized_location) holder_type(holder_type::from_raw_ptr_take_ownership(
|
||||
value_ptr_w_t, /*void_cast_raw_ptr*/ inst->is_alias));
|
||||
} else {
|
||||
new (uninitialized_location)
|
||||
holder_type(holder_type::from_raw_ptr_unowned(value_ptr_w_t));
|
||||
}
|
||||
}
|
||||
v_h.set_holder_constructed();
|
||||
}
|
||||
|
||||
#endif // PYBIND11_SMART_HOLDER_ENABLED
|
||||
|
||||
/// 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.
|
||||
@ -1963,6 +2290,18 @@ private:
|
||||
}
|
||||
};
|
||||
|
||||
#ifdef PYBIND11_SMART_HOLDER_ENABLED
|
||||
|
||||
// Supports easier switching between py::class_<T> and py::class_<T, py::smart_holder>:
|
||||
// users can simply replace the `_` in `class_` with `h` or vice versa.
|
||||
template <typename type_, typename... options>
|
||||
class classh : public class_<type_, smart_holder, options...> {
|
||||
public:
|
||||
using class_<type_, smart_holder, options...>::class_;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
/// Binds an existing constructor taking arguments Args...
|
||||
template <typename... Args>
|
||||
detail::initimpl::constructor<Args...> init() {
|
||||
|
14
include/pybind11/smart_holder.h
Normal file
14
include/pybind11/smart_holder.h
Normal file
@ -0,0 +1,14 @@
|
||||
// Copyright (c) 2021-2024 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 "pybind11.h"
|
||||
|
||||
// Legacy macros introduced with smart_holder_type_casters implementation in 2021.
|
||||
// Deprecated.
|
||||
#define PYBIND11_TYPE_CASTER_BASE_HOLDER(...)
|
||||
#define PYBIND11_SMART_HOLDER_TYPE_CASTERS(...)
|
||||
#define PYBIND11_SH_AVL(...) // "Smart_Holder if AVaiLable"
|
||||
#define PYBIND11_SH_DEF(...) // "Smart_Holder if DEFault"
|
@ -487,7 +487,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>;
|
||||
|
||||
@ -696,7 +696,7 @@ struct ItemsViewImpl : public detail::items_view {
|
||||
|
||||
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;
|
||||
|
66
include/pybind11/trampoline_self_life_support.h
Normal file
66
include/pybind11/trampoline_self_life_support.h
Normal file
@ -0,0 +1,66 @@
|
||||
// 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/internals.h"
|
||||
|
||||
#ifdef PYBIND11_SMART_HOLDER_ENABLED
|
||||
|
||||
# include "detail/common.h"
|
||||
# include "detail/using_smart_holder.h"
|
||||
# include "detail/value_and_holder.h"
|
||||
|
||||
PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE)
|
||||
|
||||
PYBIND11_NAMESPACE_BEGIN(detail)
|
||||
// PYBIND11:REMINDER: Needs refactoring of existing pybind11 code.
|
||||
inline bool deregister_instance(instance *self, void *valptr, const type_info *tinfo);
|
||||
PYBIND11_NAMESPACE_END(detail)
|
||||
|
||||
// The original core idea for this struct goes back to PyCLIF:
|
||||
// https://github.com/google/clif/blob/07f95d7e69dca2fcf7022978a55ef3acff506c19/clif/python/runtime.cc#L37
|
||||
// URL provided here mainly to give proper credit.
|
||||
struct trampoline_self_life_support {
|
||||
detail::value_and_holder v_h;
|
||||
|
||||
trampoline_self_life_support() = default;
|
||||
|
||||
void activate_life_support(const detail::value_and_holder &v_h_) {
|
||||
Py_INCREF((PyObject *) v_h_.inst);
|
||||
v_h = v_h_;
|
||||
}
|
||||
|
||||
void deactivate_life_support() {
|
||||
Py_DECREF((PyObject *) v_h.inst);
|
||||
v_h = detail::value_and_holder();
|
||||
}
|
||||
|
||||
~trampoline_self_life_support() {
|
||||
if (v_h.inst != nullptr && v_h.vh != nullptr) {
|
||||
void *value_void_ptr = v_h.value_ptr();
|
||||
if (value_void_ptr != nullptr) {
|
||||
PyGILState_STATE threadstate = PyGILState_Ensure();
|
||||
v_h.value_ptr() = nullptr;
|
||||
v_h.holder<smart_holder>().release_disowned();
|
||||
detail::deregister_instance(v_h.inst, value_void_ptr, v_h.type);
|
||||
Py_DECREF((PyObject *) v_h.inst); // Must be after deregister.
|
||||
PyGILState_Release(threadstate);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// For the next two, the default implementations generate undefined behavior (ASAN failures
|
||||
// manually verified). The reason is that v_h needs to be kept default-initialized.
|
||||
trampoline_self_life_support(const trampoline_self_life_support &) {}
|
||||
trampoline_self_life_support(trampoline_self_life_support &&) noexcept {}
|
||||
|
||||
// These should never be needed (please provide test cases if you think they are).
|
||||
trampoline_self_life_support &operator=(const trampoline_self_life_support &) = delete;
|
||||
trampoline_self_life_support &operator=(trampoline_self_life_support &&) = delete;
|
||||
};
|
||||
|
||||
PYBIND11_NAMESPACE_END(PYBIND11_NAMESPACE)
|
||||
|
||||
#endif // PYBIND11_SMART_HOLDER_ENABLED
|
@ -8,5 +8,5 @@ def _to_int(s: str) -> int | str:
|
||||
return s
|
||||
|
||||
|
||||
__version__ = "2.14.0.dev1"
|
||||
__version__ = "3.0.0.dev1"
|
||||
version_info = tuple(_to_int(s) for s in __version__.split("."))
|
||||
|
@ -115,6 +115,23 @@ set(PYBIND11_TEST_FILES
|
||||
test_callbacks
|
||||
test_chrono
|
||||
test_class
|
||||
test_class_sh_basic
|
||||
test_class_sh_disowning
|
||||
test_class_sh_disowning_mi
|
||||
test_class_sh_factory_constructors
|
||||
test_class_sh_inheritance
|
||||
test_class_sh_mi_thunks
|
||||
test_class_sh_property
|
||||
test_class_sh_property_non_owning
|
||||
test_class_sh_shared_ptr_copy_move
|
||||
test_class_sh_trampoline_basic
|
||||
test_class_sh_trampoline_self_life_support
|
||||
test_class_sh_trampoline_shared_from_this
|
||||
test_class_sh_trampoline_shared_ptr_cpp_arg
|
||||
test_class_sh_trampoline_unique_ptr
|
||||
test_class_sh_unique_ptr_custom_deleter
|
||||
test_class_sh_unique_ptr_member
|
||||
test_class_sh_virtual_py_cpp_mix
|
||||
test_const_name
|
||||
test_constants_and_functions
|
||||
test_copy_move
|
||||
@ -581,6 +598,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)
|
||||
|
||||
|
@ -43,8 +43,10 @@ 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",
|
||||
"include/pybind11/trampoline_self_life_support.h",
|
||||
"include/pybind11/type_caster_pyobject_ptr.h",
|
||||
"include/pybind11/typing.h",
|
||||
"include/pybind11/warnings.h",
|
||||
@ -55,10 +57,13 @@ detail_headers = {
|
||||
"include/pybind11/detail/common.h",
|
||||
"include/pybind11/detail/cpp_conduit.h",
|
||||
"include/pybind11/detail/descr.h",
|
||||
"include/pybind11/detail/dynamic_raw_ptr_cast_if_possible.h",
|
||||
"include/pybind11/detail/init.h",
|
||||
"include/pybind11/detail/internals.h",
|
||||
"include/pybind11/detail/struct_smart_holder.h",
|
||||
"include/pybind11/detail/type_caster_base.h",
|
||||
"include/pybind11/detail/typeid.h",
|
||||
"include/pybind11/detail/using_smart_holder.h",
|
||||
"include/pybind11/detail/value_and_holder.h",
|
||||
"include/pybind11/detail/exception_translation.h",
|
||||
}
|
||||
@ -119,6 +124,7 @@ sdist_files = {
|
||||
"LICENSE",
|
||||
"MANIFEST.in",
|
||||
"README.rst",
|
||||
"README_smart_holder.rst",
|
||||
"PKG-INFO",
|
||||
"SECURITY.md",
|
||||
}
|
||||
|
20
tests/pure_cpp/CMakeLists.txt
Normal file
20
tests/pure_cpp/CMakeLists.txt
Normal file
@ -0,0 +1,20 @@
|
||||
find_package(Catch 2.13.2)
|
||||
|
||||
if(CATCH_FOUND)
|
||||
message(STATUS "Building pure C++ tests (not depending on Python) using Catch v${CATCH_VERSION}")
|
||||
else()
|
||||
message(STATUS "Catch not detected. Interpreter tests will be skipped. Install Catch headers"
|
||||
" manually or use `cmake -DDOWNLOAD_CATCH=ON` to fetch them automatically.")
|
||||
return()
|
||||
endif()
|
||||
|
||||
add_executable(smart_holder_poc_test smart_holder_poc_test.cpp)
|
||||
pybind11_enable_warnings(smart_holder_poc_test)
|
||||
target_link_libraries(smart_holder_poc_test PRIVATE pybind11::headers Catch2::Catch2)
|
||||
|
||||
add_custom_target(
|
||||
test_pure_cpp
|
||||
COMMAND "$<TARGET_FILE:smart_holder_poc_test>"
|
||||
WORKING_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}")
|
||||
|
||||
add_dependencies(check test_pure_cpp)
|
51
tests/pure_cpp/smart_holder_poc.h
Normal file
51
tests/pure_cpp/smart_holder_poc.h
Normal file
@ -0,0 +1,51 @@
|
||||
// Copyright (c) 2020-2024 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 "pybind11/detail/struct_smart_holder.h"
|
||||
|
||||
namespace pybindit {
|
||||
namespace memory {
|
||||
namespace smart_holder_poc { // Proof-of-Concept implementations.
|
||||
|
||||
template <typename T>
|
||||
T &as_lvalue_ref(const smart_holder &hld) {
|
||||
static const char *context = "as_lvalue_ref";
|
||||
hld.ensure_is_populated(context);
|
||||
hld.ensure_has_pointee(context);
|
||||
return *hld.as_raw_ptr_unowned<T>();
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
T &&as_rvalue_ref(const smart_holder &hld) {
|
||||
static const char *context = "as_rvalue_ref";
|
||||
hld.ensure_is_populated(context);
|
||||
hld.ensure_has_pointee(context);
|
||||
return std::move(*hld.as_raw_ptr_unowned<T>());
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
T *as_raw_ptr_release_ownership(smart_holder &hld,
|
||||
const char *context = "as_raw_ptr_release_ownership") {
|
||||
hld.ensure_can_release_ownership(context);
|
||||
T *raw_ptr = hld.as_raw_ptr_unowned<T>();
|
||||
hld.release_ownership();
|
||||
return raw_ptr;
|
||||
}
|
||||
|
||||
template <typename T, typename D = std::default_delete<T>>
|
||||
std::unique_ptr<T, D> as_unique_ptr(smart_holder &hld) {
|
||||
static const char *context = "as_unique_ptr";
|
||||
hld.ensure_compatible_rtti_uqp_del<T, D>(context);
|
||||
hld.ensure_use_count_1(context);
|
||||
T *raw_ptr = hld.as_raw_ptr_unowned<T>();
|
||||
hld.release_ownership();
|
||||
// KNOWN DEFECT (see PR #4850): Does not copy the deleter.
|
||||
return std::unique_ptr<T, D>(raw_ptr);
|
||||
}
|
||||
|
||||
} // namespace smart_holder_poc
|
||||
} // namespace memory
|
||||
} // namespace pybindit
|
415
tests/pure_cpp/smart_holder_poc_test.cpp
Normal file
415
tests/pure_cpp/smart_holder_poc_test.cpp
Normal file
@ -0,0 +1,415 @@
|
||||
#include "smart_holder_poc.h"
|
||||
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
|
||||
// Catch uses _ internally, which breaks gettext style defines
|
||||
#ifdef _
|
||||
# undef _
|
||||
#endif
|
||||
|
||||
#define CATCH_CONFIG_MAIN
|
||||
#include "catch.hpp"
|
||||
|
||||
using pybindit::memory::smart_holder;
|
||||
namespace poc = pybindit::memory::smart_holder_poc;
|
||||
|
||||
namespace helpers {
|
||||
|
||||
struct movable_int {
|
||||
int valu;
|
||||
explicit movable_int(int v) : valu{v} {}
|
||||
movable_int(movable_int &&other) noexcept : valu(other.valu) { other.valu = 91; }
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
struct functor_builtin_delete {
|
||||
void operator()(T *ptr) { delete ptr; }
|
||||
#if (defined(__GNUC__) && __GNUC__ == 4 && __GNUC_MINOR__ == 8) \
|
||||
|| (defined(__clang_major__) && __clang_major__ == 3 && __clang_minor__ == 6)
|
||||
// Workaround for these errors:
|
||||
// gcc 4.8.5: too many initializers for 'helpers::functor_builtin_delete<int>'
|
||||
// clang 3.6: excess elements in struct initializer
|
||||
functor_builtin_delete() = default;
|
||||
functor_builtin_delete(const functor_builtin_delete &) {}
|
||||
functor_builtin_delete(functor_builtin_delete &&) {}
|
||||
#endif
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
struct functor_other_delete : functor_builtin_delete<T> {};
|
||||
|
||||
struct indestructible_int {
|
||||
int valu;
|
||||
explicit indestructible_int(int v) : valu{v} {}
|
||||
|
||||
private:
|
||||
~indestructible_int() = default;
|
||||
};
|
||||
|
||||
struct base {
|
||||
virtual int get() { return 10; }
|
||||
virtual ~base() = default;
|
||||
};
|
||||
|
||||
struct derived : public base {
|
||||
int get() override { return 100; }
|
||||
};
|
||||
|
||||
} // 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(poc::as_lvalue_ref<int>(hld) == 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(poc::as_rvalue_ref<helpers::movable_int>(hld));
|
||||
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(poc::as_raw_ptr_release_ownership<int>(hld),
|
||||
"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(poc::as_unique_ptr<int>(hld),
|
||||
"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((poc::as_unique_ptr<int, helpers::functor_builtin_delete<int>>(hld)),
|
||||
"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(poc::as_lvalue_ref<int>(hld) == 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>(poc::as_raw_ptr_release_ownership<int>(hld));
|
||||
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(poc::as_raw_ptr_release_ownership<int>(hld),
|
||||
"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 = poc::as_unique_ptr<int>(hld);
|
||||
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(poc::as_unique_ptr<int>(hld),
|
||||
"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((poc::as_unique_ptr<int, helpers::functor_builtin_delete<int>>(hld)),
|
||||
"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_raw_ptr_take_ownership+disown+reclaim_disowned", "[S]") {
|
||||
auto hld = smart_holder::from_raw_ptr_take_ownership(new int(19));
|
||||
std::unique_ptr<int> new_owner(hld.as_raw_ptr_unowned<int>());
|
||||
hld.disown();
|
||||
REQUIRE(poc::as_lvalue_ref<int>(hld) == 19);
|
||||
REQUIRE(*new_owner == 19);
|
||||
hld.reclaim_disowned(); // Manually veriified: without this, clang++ -fsanitize=address reports
|
||||
// "detected memory leaks".
|
||||
// NOLINTNEXTLINE(bugprone-unused-return-value)
|
||||
(void) new_owner.release(); // Manually verified: without this, clang++ -fsanitize=address
|
||||
// reports "attempting double-free".
|
||||
REQUIRE(poc::as_lvalue_ref<int>(hld) == 19);
|
||||
REQUIRE(new_owner.get() == nullptr);
|
||||
}
|
||||
|
||||
TEST_CASE("from_raw_ptr_take_ownership+disown+release_disowned", "[S]") {
|
||||
auto hld = smart_holder::from_raw_ptr_take_ownership(new int(19));
|
||||
std::unique_ptr<int> new_owner(hld.as_raw_ptr_unowned<int>());
|
||||
hld.disown();
|
||||
REQUIRE(poc::as_lvalue_ref<int>(hld) == 19);
|
||||
REQUIRE(*new_owner == 19);
|
||||
hld.release_disowned();
|
||||
REQUIRE(!hld.has_pointee());
|
||||
}
|
||||
|
||||
TEST_CASE("from_raw_ptr_take_ownership+disown+ensure_is_not_disowned", "[E]") {
|
||||
const char *context = "test_case";
|
||||
auto hld = smart_holder::from_raw_ptr_take_ownership(new int(19));
|
||||
hld.ensure_is_not_disowned(context); // Does not throw.
|
||||
std::unique_ptr<int> new_owner(hld.as_raw_ptr_unowned<int>());
|
||||
hld.disown();
|
||||
REQUIRE_THROWS_WITH(hld.ensure_is_not_disowned(context),
|
||||
"Holder was disowned already (test_case).");
|
||||
}
|
||||
|
||||
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(poc::as_lvalue_ref<int>(hld) == 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>(poc::as_raw_ptr_release_ownership<int>(hld));
|
||||
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(poc::as_raw_ptr_release_ownership<int>(hld),
|
||||
"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 = poc::as_unique_ptr<int>(hld);
|
||||
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(poc::as_unique_ptr<int>(hld),
|
||||
"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((poc::as_unique_ptr<int, helpers::functor_builtin_delete<int>>(hld)),
|
||||
"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_derived+as_unique_ptr_base", "[S]") {
|
||||
std::unique_ptr<helpers::derived> orig_owner(new helpers::derived());
|
||||
auto hld = smart_holder::from_unique_ptr(std::move(orig_owner));
|
||||
REQUIRE(orig_owner.get() == nullptr);
|
||||
std::unique_ptr<helpers::base> new_owner = poc::as_unique_ptr<helpers::base>(hld);
|
||||
REQUIRE(!hld.has_pointee());
|
||||
REQUIRE(new_owner->get() == 100);
|
||||
}
|
||||
|
||||
TEST_CASE("from_unique_ptr_derived+as_unique_ptr_base2", "[E]") {
|
||||
std::unique_ptr<helpers::derived, helpers::functor_other_delete<helpers::derived>> orig_owner(
|
||||
new helpers::derived());
|
||||
auto hld = smart_holder::from_unique_ptr(std::move(orig_owner));
|
||||
REQUIRE(orig_owner.get() == nullptr);
|
||||
REQUIRE_THROWS_WITH(
|
||||
(poc::as_unique_ptr<helpers::base, helpers::functor_builtin_delete<helpers::base>>(hld)),
|
||||
"Incompatible unique_ptr deleter (as_unique_ptr).");
|
||||
}
|
||||
|
||||
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(poc::as_lvalue_ref<int>(hld) == 19);
|
||||
}
|
||||
|
||||
TEST_CASE("from_unique_ptr_with_std_function_deleter+as_lvalue_ref", "[S]") {
|
||||
std::unique_ptr<int, std::function<void(const int *)>> orig_owner(
|
||||
new int(19), [](const int *raw_ptr) { delete raw_ptr; });
|
||||
auto hld = smart_holder::from_unique_ptr(std::move(orig_owner));
|
||||
REQUIRE(orig_owner.get() == nullptr);
|
||||
REQUIRE(poc::as_lvalue_ref<int>(hld) == 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(poc::as_raw_ptr_release_ownership<int>(hld),
|
||||
"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(poc::as_unique_ptr<int>(hld),
|
||||
"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
|
||||
= poc::as_unique_ptr<int, helpers::functor_builtin_delete<int>>(hld);
|
||||
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((poc::as_unique_ptr<int, helpers::functor_other_delete<int>>(hld)),
|
||||
"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(poc::as_lvalue_ref<int>(hld) == 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(poc::as_raw_ptr_release_ownership<int>(hld),
|
||||
"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(poc::as_unique_ptr<int>(hld),
|
||||
"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((poc::as_unique_ptr<int, helpers::functor_builtin_delete<int>>(hld)),
|
||||
"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(poc::as_lvalue_ref<int>(hld), "Unpopulated holder (as_lvalue_ref).");
|
||||
}
|
||||
|
||||
TEST_CASE("error_disowned_holder", "[E]") {
|
||||
auto hld = smart_holder::from_raw_ptr_take_ownership(new int(19));
|
||||
poc::as_unique_ptr<int>(hld);
|
||||
REQUIRE_THROWS_WITH(poc::as_lvalue_ref<int>(hld), "Disowned holder (as_lvalue_ref).");
|
||||
}
|
||||
|
||||
TEST_CASE("error_cannot_disown_nullptr", "[E]") {
|
||||
auto hld = smart_holder::from_raw_ptr_take_ownership(new int(19));
|
||||
poc::as_unique_ptr<int>(hld);
|
||||
REQUIRE_THROWS_WITH(poc::as_unique_ptr<int>(hld), "Cannot disown nullptr (as_unique_ptr).");
|
||||
}
|
||||
|
||||
TEST_CASE("indestructible_int-from_raw_ptr_unowned+as_raw_ptr_unowned", "[S]") {
|
||||
using zombie = helpers::indestructible_int;
|
||||
// Using placement new instead of plain new, to not trigger leak sanitizer errors.
|
||||
static std::aligned_storage<sizeof(zombie), alignof(zombie)>::type memory_block[1];
|
||||
auto *value = new (memory_block) zombie(19);
|
||||
auto hld = smart_holder::from_raw_ptr_unowned(value);
|
||||
REQUIRE(hld.as_raw_ptr_unowned<zombie>()->valu == 19);
|
||||
}
|
||||
|
||||
TEST_CASE("indestructible_int-from_raw_ptr_take_ownership", "[E]") {
|
||||
helpers::indestructible_int *value = nullptr;
|
||||
REQUIRE_THROWS_WITH(smart_holder::from_raw_ptr_take_ownership(value),
|
||||
"Pointee is not destructible (from_raw_ptr_take_ownership).");
|
||||
}
|
||||
|
||||
TEST_CASE("from_raw_ptr_take_ownership+as_shared_ptr-outliving_smart_holder", "[S]") {
|
||||
// Exercises guarded_builtin_delete flag_ptr validity past destruction of smart_holder.
|
||||
std::shared_ptr<int> longer_living;
|
||||
{
|
||||
auto hld = smart_holder::from_raw_ptr_take_ownership(new int(19));
|
||||
longer_living = hld.as_shared_ptr<int>();
|
||||
}
|
||||
REQUIRE(*longer_living == 19);
|
||||
}
|
||||
|
||||
TEST_CASE("from_unique_ptr_with_deleter+as_shared_ptr-outliving_smart_holder", "[S]") {
|
||||
// Exercises guarded_custom_deleter flag_ptr validity past destruction of smart_holder.
|
||||
std::shared_ptr<int> longer_living;
|
||||
{
|
||||
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));
|
||||
longer_living = hld.as_shared_ptr<int>();
|
||||
}
|
||||
REQUIRE(*longer_living == 19);
|
||||
}
|
@ -89,6 +89,16 @@ TEST_SUBMODULE(class_, m) {
|
||||
.def_static("__new__",
|
||||
[](const py::object &) { return NoConstructorNew::new_instance(); });
|
||||
|
||||
// test_pass_unique_ptr
|
||||
struct ToBeHeldByUniquePtr {};
|
||||
py::class_<ToBeHeldByUniquePtr, std::unique_ptr<ToBeHeldByUniquePtr>>(m, "ToBeHeldByUniquePtr")
|
||||
.def(py::init<>());
|
||||
#ifdef PYBIND11_SMART_HOLDER_ENABLED
|
||||
m.def("pass_unique_ptr", [](std::unique_ptr<ToBeHeldByUniquePtr> &&) {});
|
||||
#else
|
||||
m.attr("pass_unique_ptr") = py::none();
|
||||
#endif
|
||||
|
||||
// test_inheritance
|
||||
class Pet {
|
||||
public:
|
||||
@ -211,11 +221,12 @@ TEST_SUBMODULE(class_, m) {
|
||||
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");
|
||||
});
|
||||
@ -609,8 +620,10 @@ CHECK_NOALIAS(8);
|
||||
CHECK_HOLDER(1, unique);
|
||||
CHECK_HOLDER(2, unique);
|
||||
CHECK_HOLDER(3, unique);
|
||||
#ifndef PYBIND11_ACTUALLY_USING_SMART_HOLDER_AS_DEFAULT
|
||||
CHECK_HOLDER(4, unique);
|
||||
CHECK_HOLDER(5, unique);
|
||||
#endif
|
||||
CHECK_HOLDER(6, shared);
|
||||
CHECK_HOLDER(7, shared);
|
||||
CHECK_HOLDER(8, shared);
|
||||
|
@ -41,6 +41,18 @@ def test_instance_new():
|
||||
assert cstats.alive() == 0
|
||||
|
||||
|
||||
def test_pass_unique_ptr():
|
||||
obj = m.ToBeHeldByUniquePtr()
|
||||
if m.pass_unique_ptr is None:
|
||||
pytest.skip("smart_holder not available.")
|
||||
with pytest.raises(RuntimeError) as execinfo:
|
||||
m.pass_unique_ptr(obj)
|
||||
assert str(execinfo.value).startswith(
|
||||
"Passing `std::unique_ptr<T>` from Python to C++ requires `py::classh` (with T = "
|
||||
)
|
||||
assert "ToBeHeldByUniquePtr" in str(execinfo.value)
|
||||
|
||||
|
||||
def test_type():
|
||||
assert m.check_type(1) == m.DerivedClass1
|
||||
with pytest.raises(RuntimeError) as execinfo:
|
||||
|
267
tests/test_class_sh_basic.cpp
Normal file
267
tests/test_class_sh_basic.cpp
Normal file
@ -0,0 +1,267 @@
|
||||
#include <pybind11/smart_holder.h>
|
||||
|
||||
#include "pybind11_tests.h"
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace pybind11_tests {
|
||||
namespace class_sh_basic {
|
||||
|
||||
struct atyp { // Short for "any type".
|
||||
std::string mtxt;
|
||||
atyp() : mtxt("DefaultConstructor") {}
|
||||
explicit atyp(const std::string &mtxt_) : mtxt(mtxt_) {}
|
||||
atyp(const atyp &other) { mtxt = other.mtxt + "_CpCtor"; }
|
||||
atyp(atyp &&other) noexcept { mtxt = other.mtxt + "_MvCtor"; }
|
||||
};
|
||||
|
||||
struct uconsumer { // unique_ptr consumer
|
||||
std::unique_ptr<atyp> held;
|
||||
bool valid() const { return static_cast<bool>(held); }
|
||||
|
||||
void pass_valu(std::unique_ptr<atyp> obj) { held = std::move(obj); }
|
||||
void pass_rref(std::unique_ptr<atyp> &&obj) { held = std::move(obj); }
|
||||
std::unique_ptr<atyp> rtrn_valu() { return std::move(held); }
|
||||
std::unique_ptr<atyp> &rtrn_lref() { return held; }
|
||||
const std::unique_ptr<atyp> &rtrn_cref() const { return held; }
|
||||
};
|
||||
|
||||
/// Custom deleter that is default constructible.
|
||||
struct custom_deleter {
|
||||
std::string trace_txt;
|
||||
|
||||
custom_deleter() = default;
|
||||
explicit custom_deleter(const std::string &trace_txt_) : trace_txt(trace_txt_) {}
|
||||
|
||||
custom_deleter(const custom_deleter &other) { trace_txt = other.trace_txt + "_CpCtor"; }
|
||||
|
||||
custom_deleter &operator=(const custom_deleter &rhs) {
|
||||
trace_txt = rhs.trace_txt + "_CpLhs";
|
||||
return *this;
|
||||
}
|
||||
|
||||
custom_deleter(custom_deleter &&other) noexcept {
|
||||
trace_txt = other.trace_txt + "_MvCtorTo";
|
||||
other.trace_txt += "_MvCtorFrom";
|
||||
}
|
||||
|
||||
custom_deleter &operator=(custom_deleter &&rhs) noexcept {
|
||||
trace_txt = rhs.trace_txt + "_MvLhs";
|
||||
rhs.trace_txt += "_MvRhs";
|
||||
return *this;
|
||||
}
|
||||
|
||||
void operator()(atyp *p) const { std::default_delete<atyp>()(p); }
|
||||
void operator()(const atyp *p) const { std::default_delete<const atyp>()(p); }
|
||||
};
|
||||
static_assert(std::is_default_constructible<custom_deleter>::value, "");
|
||||
|
||||
/// Custom deleter that is not default constructible.
|
||||
struct custom_deleter_nd : custom_deleter {
|
||||
custom_deleter_nd() = delete;
|
||||
explicit custom_deleter_nd(const std::string &trace_txt_) : custom_deleter(trace_txt_) {}
|
||||
};
|
||||
static_assert(!std::is_default_constructible<custom_deleter_nd>::value, "");
|
||||
|
||||
// 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; } // NOLINT
|
||||
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::make_shared<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; } // NOLINT
|
||||
std::string pass_shcp(std::shared_ptr<atyp const> obj) { return "pass_shcp:" + obj->mtxt; } // NOLINT
|
||||
|
||||
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; }
|
||||
|
||||
std::unique_ptr<atyp, custom_deleter> rtrn_udmp_del() { return std::unique_ptr<atyp, custom_deleter>(new atyp{"rtrn_udmp_del"}, custom_deleter{"udmp_deleter"}); }
|
||||
std::unique_ptr<atyp const, custom_deleter> rtrn_udcp_del() { return std::unique_ptr<atyp const, custom_deleter>(new atyp{"rtrn_udcp_del"}, custom_deleter{"udcp_deleter"}); }
|
||||
|
||||
std::string pass_udmp_del(std::unique_ptr<atyp, custom_deleter> obj) { return "pass_udmp_del:" + obj->mtxt + "," + obj.get_deleter().trace_txt; }
|
||||
std::string pass_udcp_del(std::unique_ptr<atyp const, custom_deleter> obj) { return "pass_udcp_del:" + obj->mtxt + "," + obj.get_deleter().trace_txt; }
|
||||
|
||||
std::unique_ptr<atyp, custom_deleter_nd> rtrn_udmp_del_nd() { return std::unique_ptr<atyp, custom_deleter_nd>(new atyp{"rtrn_udmp_del_nd"}, custom_deleter_nd{"udmp_deleter_nd"}); }
|
||||
std::unique_ptr<atyp const, custom_deleter_nd> rtrn_udcp_del_nd() { return std::unique_ptr<atyp const, custom_deleter_nd>(new atyp{"rtrn_udcp_del_nd"}, custom_deleter_nd{"udcp_deleter_nd"}); }
|
||||
|
||||
std::string pass_udmp_del_nd(std::unique_ptr<atyp, custom_deleter_nd> obj) { return "pass_udmp_del_nd:" + obj->mtxt + "," + obj.get_deleter().trace_txt; }
|
||||
std::string pass_udcp_del_nd(std::unique_ptr<atyp const, custom_deleter_nd> obj) { return "pass_udcp_del_nd:" + obj->mtxt + "," + obj.get_deleter().trace_txt; }
|
||||
|
||||
// clang-format on
|
||||
|
||||
// Helpers for testing.
|
||||
std::string get_mtxt(atyp const &obj) { return obj.mtxt; }
|
||||
std::ptrdiff_t get_ptr(atyp const &obj) { return reinterpret_cast<std::ptrdiff_t>(&obj); }
|
||||
|
||||
std::unique_ptr<atyp> unique_ptr_roundtrip(std::unique_ptr<atyp> obj) { return obj; }
|
||||
|
||||
std::string pass_unique_ptr_cref(const std::unique_ptr<atyp> &obj) { return obj->mtxt; }
|
||||
|
||||
const std::unique_ptr<atyp> &rtrn_unique_ptr_cref(const std::string &mtxt) {
|
||||
static std::unique_ptr<atyp> obj{new atyp{"static_ctor_arg"}};
|
||||
if (!mtxt.empty()) {
|
||||
obj->mtxt = mtxt;
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
|
||||
const std::unique_ptr<atyp> &unique_ptr_cref_roundtrip(const std::unique_ptr<atyp> &obj) {
|
||||
return obj;
|
||||
}
|
||||
|
||||
struct SharedPtrStash {
|
||||
std::vector<std::shared_ptr<const atyp>> stash;
|
||||
void Add(const std::shared_ptr<const atyp> &obj) { stash.push_back(obj); }
|
||||
};
|
||||
|
||||
class LocalUnusualOpRef : UnusualOpRef {}; // To avoid clashing with `py::class_<UnusualOpRef>`.
|
||||
py::object CastUnusualOpRefConstRef(const LocalUnusualOpRef &cref) { return py::cast(cref); }
|
||||
py::object CastUnusualOpRefMovable(LocalUnusualOpRef &&mvbl) { return py::cast(std::move(mvbl)); }
|
||||
|
||||
} // namespace class_sh_basic
|
||||
} // namespace pybind11_tests
|
||||
|
||||
PYBIND11_SMART_HOLDER_TYPE_CASTERS(pybind11_tests::class_sh_basic::atyp)
|
||||
PYBIND11_SMART_HOLDER_TYPE_CASTERS(pybind11_tests::class_sh_basic::uconsumer)
|
||||
PYBIND11_SMART_HOLDER_TYPE_CASTERS(pybind11_tests::class_sh_basic::SharedPtrStash)
|
||||
PYBIND11_SMART_HOLDER_TYPE_CASTERS(pybind11_tests::class_sh_basic::LocalUnusualOpRef)
|
||||
|
||||
namespace pybind11_tests {
|
||||
namespace class_sh_basic {
|
||||
|
||||
TEST_SUBMODULE(class_sh_basic, m) {
|
||||
m.attr("defined_PYBIND11_SMART_HOLDER_ENABLED") =
|
||||
#ifndef PYBIND11_SMART_HOLDER_ENABLED
|
||||
false;
|
||||
#else
|
||||
true;
|
||||
|
||||
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);
|
||||
|
||||
m.def("rtrn_udmp_del", rtrn_udmp_del);
|
||||
m.def("rtrn_udcp_del", rtrn_udcp_del);
|
||||
|
||||
m.def("pass_udmp_del", pass_udmp_del);
|
||||
m.def("pass_udcp_del", pass_udcp_del);
|
||||
|
||||
m.def("rtrn_udmp_del_nd", rtrn_udmp_del_nd);
|
||||
m.def("rtrn_udcp_del_nd", rtrn_udcp_del_nd);
|
||||
|
||||
m.def("pass_udmp_del_nd", pass_udmp_del_nd);
|
||||
m.def("pass_udcp_del_nd", pass_udcp_del_nd);
|
||||
|
||||
py::classh<uconsumer>(m, "uconsumer")
|
||||
.def(py::init<>())
|
||||
.def("valid", &uconsumer::valid)
|
||||
.def("pass_valu", &uconsumer::pass_valu)
|
||||
.def("pass_rref", &uconsumer::pass_rref)
|
||||
.def("rtrn_valu", &uconsumer::rtrn_valu)
|
||||
.def("rtrn_lref", &uconsumer::rtrn_lref)
|
||||
.def("rtrn_cref", &uconsumer::rtrn_cref);
|
||||
|
||||
// Helpers for testing.
|
||||
// These require selected functions above to work first, as indicated:
|
||||
m.def("get_mtxt", get_mtxt); // pass_cref
|
||||
m.def("get_ptr", get_ptr); // pass_cref
|
||||
|
||||
m.def("unique_ptr_roundtrip", unique_ptr_roundtrip); // pass_uqmp, rtrn_uqmp
|
||||
|
||||
m.def("pass_unique_ptr_cref", pass_unique_ptr_cref);
|
||||
m.def("rtrn_unique_ptr_cref", rtrn_unique_ptr_cref);
|
||||
m.def("unique_ptr_cref_roundtrip", unique_ptr_cref_roundtrip);
|
||||
|
||||
py::classh<SharedPtrStash>(m, "SharedPtrStash")
|
||||
.def(py::init<>())
|
||||
.def("Add", &SharedPtrStash::Add, py::arg("obj"));
|
||||
|
||||
m.def("py_type_handle_of_atyp", []() {
|
||||
return py::type::handle_of<atyp>(); // Exercises static_cast in this function.
|
||||
});
|
||||
|
||||
// Checks for type names used as arguments
|
||||
m.def("args_shared_ptr", [](std::shared_ptr<atyp> p) { return p; });
|
||||
m.def("args_shared_ptr_const", [](std::shared_ptr<atyp const> p) { return p; });
|
||||
m.def("args_unique_ptr", [](std::unique_ptr<atyp> p) { return p; });
|
||||
m.def("args_unique_ptr_const", [](std::unique_ptr<atyp const> p) { return p; });
|
||||
|
||||
// Make sure unique_ptr type caster accept automatic_reference return value policy.
|
||||
m.def(
|
||||
"rtrn_uq_automatic_reference",
|
||||
[]() { return std::unique_ptr<atyp>(new atyp("rtrn_uq_automatic_reference")); },
|
||||
pybind11::return_value_policy::automatic_reference);
|
||||
|
||||
m.def("pass_shared_ptr_ptr", [](std::shared_ptr<atyp> *) {});
|
||||
|
||||
py::classh<LocalUnusualOpRef>(m, "LocalUnusualOpRef");
|
||||
m.def("CallCastUnusualOpRefConstRef",
|
||||
[]() { return CastUnusualOpRefConstRef(LocalUnusualOpRef()); });
|
||||
m.def("CallCastUnusualOpRefMovable",
|
||||
[]() { return CastUnusualOpRefMovable(LocalUnusualOpRef()); });
|
||||
#endif // PYBIND11_SMART_HOLDER_ENABLED
|
||||
}
|
||||
|
||||
} // namespace class_sh_basic
|
||||
} // namespace pybind11_tests
|
249
tests/test_class_sh_basic.py
Normal file
249
tests/test_class_sh_basic.py
Normal file
@ -0,0 +1,249 @@
|
||||
# Importing re before pytest after observing a PyPy CI flake when importing pytest first.
|
||||
from __future__ import annotations
|
||||
|
||||
import re
|
||||
|
||||
import pytest
|
||||
|
||||
from pybind11_tests import class_sh_basic as m
|
||||
|
||||
if not m.defined_PYBIND11_SMART_HOLDER_ENABLED:
|
||||
pytest.skip("smart_holder not available.", allow_module_level=True)
|
||||
|
||||
|
||||
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", "regex_expected"),
|
||||
[
|
||||
(
|
||||
m.pass_udmp_del,
|
||||
m.rtrn_udmp_del,
|
||||
"pass_udmp_del:rtrn_udmp_del,udmp_deleter(_MvCtorTo)*_MvCtorTo",
|
||||
),
|
||||
(
|
||||
m.pass_udcp_del,
|
||||
m.rtrn_udcp_del,
|
||||
"pass_udcp_del:rtrn_udcp_del,udcp_deleter(_MvCtorTo)*_MvCtorTo",
|
||||
),
|
||||
(
|
||||
m.pass_udmp_del_nd,
|
||||
m.rtrn_udmp_del_nd,
|
||||
"pass_udmp_del_nd:rtrn_udmp_del_nd,udmp_deleter_nd(_MvCtorTo)*_MvCtorTo",
|
||||
),
|
||||
(
|
||||
m.pass_udcp_del_nd,
|
||||
m.rtrn_udcp_del_nd,
|
||||
"pass_udcp_del_nd:rtrn_udcp_del_nd,udcp_deleter_nd(_MvCtorTo)*_MvCtorTo",
|
||||
),
|
||||
],
|
||||
)
|
||||
def test_deleter_roundtrip(pass_f, rtrn_f, regex_expected):
|
||||
assert re.match(regex_expected, pass_f(rtrn_f()))
|
||||
|
||||
|
||||
@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(ValueError) as exc_info:
|
||||
pass_f(obj)
|
||||
assert str(exc_info.value) == (
|
||||
"Missing value for wrapped C++ type"
|
||||
+ " `pybind11_tests::class_sh_basic::atyp`:"
|
||||
+ " Python instance was disowned."
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("pass_f", "rtrn_f"),
|
||||
[
|
||||
(m.pass_uqmp, m.rtrn_uqmp),
|
||||
(m.pass_uqcp, m.rtrn_uqcp),
|
||||
(m.pass_udmp, m.rtrn_udmp),
|
||||
(m.pass_udcp, m.rtrn_udcp),
|
||||
],
|
||||
)
|
||||
def test_cannot_disown_use_count_ne_1(pass_f, rtrn_f):
|
||||
obj = rtrn_f()
|
||||
stash = m.SharedPtrStash()
|
||||
stash.Add(obj)
|
||||
with pytest.raises(ValueError) as exc_info:
|
||||
pass_f(obj)
|
||||
assert str(exc_info.value) == ("Cannot disown use_count != 1 (load_as_unique_ptr).")
|
||||
|
||||
|
||||
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_pass_unique_ptr_cref():
|
||||
obj = m.atyp("ctor_arg")
|
||||
assert re.match("ctor_arg(_MvCtor)*_MvCtor", m.get_mtxt(obj))
|
||||
assert re.match("ctor_arg(_MvCtor)*_MvCtor", m.pass_unique_ptr_cref(obj))
|
||||
assert re.match("ctor_arg(_MvCtor)*_MvCtor", m.get_mtxt(obj))
|
||||
|
||||
|
||||
def test_rtrn_unique_ptr_cref():
|
||||
obj0 = m.rtrn_unique_ptr_cref("")
|
||||
assert m.get_mtxt(obj0) == "static_ctor_arg"
|
||||
obj1 = m.rtrn_unique_ptr_cref("passed_mtxt_1")
|
||||
assert m.get_mtxt(obj1) == "passed_mtxt_1"
|
||||
assert m.get_mtxt(obj0) == "passed_mtxt_1"
|
||||
assert obj0 is obj1
|
||||
|
||||
|
||||
def test_unique_ptr_cref_roundtrip(num_round_trips=1000):
|
||||
# Multiple roundtrips to stress-test implementation.
|
||||
orig = m.atyp("passenger")
|
||||
mtxt_orig = m.get_mtxt(orig)
|
||||
recycled = orig
|
||||
for _ in range(num_round_trips):
|
||||
recycled = m.unique_ptr_cref_roundtrip(recycled)
|
||||
assert recycled is orig
|
||||
assert m.get_mtxt(recycled) == mtxt_orig
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("pass_f", "rtrn_f", "moved_out", "moved_in"),
|
||||
[
|
||||
(m.uconsumer.pass_valu, m.uconsumer.rtrn_valu, True, True),
|
||||
(m.uconsumer.pass_rref, m.uconsumer.rtrn_valu, True, True),
|
||||
(m.uconsumer.pass_valu, m.uconsumer.rtrn_lref, True, False),
|
||||
(m.uconsumer.pass_valu, m.uconsumer.rtrn_cref, True, False),
|
||||
],
|
||||
)
|
||||
def test_unique_ptr_consumer_roundtrip(pass_f, rtrn_f, moved_out, moved_in):
|
||||
c = m.uconsumer()
|
||||
assert not c.valid()
|
||||
recycled = m.atyp("passenger")
|
||||
mtxt_orig = m.get_mtxt(recycled)
|
||||
assert re.match("passenger_(MvCtor){1,2}", mtxt_orig)
|
||||
|
||||
pass_f(c, recycled)
|
||||
if moved_out:
|
||||
with pytest.raises(ValueError) as excinfo:
|
||||
m.get_mtxt(recycled)
|
||||
assert "Python instance was disowned" in str(excinfo.value)
|
||||
|
||||
recycled = rtrn_f(c)
|
||||
assert c.valid() != moved_in
|
||||
assert m.get_mtxt(recycled) == mtxt_orig
|
||||
|
||||
|
||||
def test_py_type_handle_of_atyp():
|
||||
obj = m.py_type_handle_of_atyp()
|
||||
assert obj.__class__.__name__ == "pybind11_type"
|
||||
|
||||
|
||||
def test_function_signatures(doc):
|
||||
assert (
|
||||
doc(m.args_shared_ptr)
|
||||
== "args_shared_ptr(arg0: m.class_sh_basic.atyp) -> m.class_sh_basic.atyp"
|
||||
)
|
||||
assert (
|
||||
doc(m.args_shared_ptr_const)
|
||||
== "args_shared_ptr_const(arg0: m.class_sh_basic.atyp) -> m.class_sh_basic.atyp"
|
||||
)
|
||||
assert (
|
||||
doc(m.args_unique_ptr)
|
||||
== "args_unique_ptr(arg0: m.class_sh_basic.atyp) -> m.class_sh_basic.atyp"
|
||||
)
|
||||
assert (
|
||||
doc(m.args_unique_ptr_const)
|
||||
== "args_unique_ptr_const(arg0: m.class_sh_basic.atyp) -> m.class_sh_basic.atyp"
|
||||
)
|
||||
|
||||
|
||||
def test_unique_ptr_return_value_policy_automatic_reference():
|
||||
assert m.get_mtxt(m.rtrn_uq_automatic_reference()) == "rtrn_uq_automatic_reference"
|
||||
|
||||
|
||||
def test_pass_shared_ptr_ptr():
|
||||
obj = m.atyp()
|
||||
with pytest.raises(RuntimeError) as excinfo:
|
||||
m.pass_shared_ptr_ptr(obj)
|
||||
assert str(excinfo.value) == (
|
||||
"Passing `std::shared_ptr<T> *` from Python to C++ is not supported"
|
||||
" (inherently unsafe)."
|
||||
)
|
||||
|
||||
|
||||
def test_unusual_op_ref():
|
||||
# Merely to test that this still exists and built successfully.
|
||||
assert m.CallCastUnusualOpRefConstRef().__class__.__name__ == "LocalUnusualOpRef"
|
||||
assert m.CallCastUnusualOpRefMovable().__class__.__name__ == "LocalUnusualOpRef"
|
53
tests/test_class_sh_disowning.cpp
Normal file
53
tests/test_class_sh_disowning.cpp
Normal file
@ -0,0 +1,53 @@
|
||||
#include <pybind11/smart_holder.h>
|
||||
|
||||
#include "pybind11_tests.h"
|
||||
|
||||
#include <memory>
|
||||
|
||||
namespace pybind11_tests {
|
||||
namespace class_sh_disowning {
|
||||
|
||||
template <int SerNo> // Using int as a trick to easily generate a series of types.
|
||||
struct Atype {
|
||||
int val = 0;
|
||||
explicit Atype(int val_) : val{val_} {}
|
||||
int get() const { return val * 10 + SerNo; }
|
||||
};
|
||||
|
||||
int same_twice(std::unique_ptr<Atype<1>> at1a, std::unique_ptr<Atype<1>> at1b) {
|
||||
return at1a->get() * 100 + at1b->get() * 10;
|
||||
}
|
||||
|
||||
int mixed(std::unique_ptr<Atype<1>> at1, std::unique_ptr<Atype<2>> at2) {
|
||||
return at1->get() * 200 + at2->get() * 20;
|
||||
}
|
||||
|
||||
int overloaded(std::unique_ptr<Atype<1>> at1, int i) { return at1->get() * 30 + i; }
|
||||
int overloaded(std::unique_ptr<Atype<2>> at2, int i) { return at2->get() * 40 + i; }
|
||||
|
||||
} // namespace class_sh_disowning
|
||||
} // namespace pybind11_tests
|
||||
|
||||
PYBIND11_SMART_HOLDER_TYPE_CASTERS(pybind11_tests::class_sh_disowning::Atype<1>)
|
||||
PYBIND11_SMART_HOLDER_TYPE_CASTERS(pybind11_tests::class_sh_disowning::Atype<2>)
|
||||
|
||||
TEST_SUBMODULE(class_sh_disowning, m) {
|
||||
m.attr("defined_PYBIND11_SMART_HOLDER_ENABLED") =
|
||||
#ifndef PYBIND11_SMART_HOLDER_ENABLED
|
||||
false;
|
||||
#else
|
||||
true;
|
||||
|
||||
using namespace pybind11_tests::class_sh_disowning;
|
||||
|
||||
py::classh<Atype<1>>(m, "Atype1").def(py::init<int>()).def("get", &Atype<1>::get);
|
||||
py::classh<Atype<2>>(m, "Atype2").def(py::init<int>()).def("get", &Atype<2>::get);
|
||||
|
||||
m.def("same_twice", same_twice);
|
||||
|
||||
m.def("mixed", mixed);
|
||||
|
||||
m.def("overloaded", (int (*)(std::unique_ptr<Atype<1>>, int)) & overloaded);
|
||||
m.def("overloaded", (int (*)(std::unique_ptr<Atype<2>>, int)) & overloaded);
|
||||
#endif // PYBIND11_SMART_HOLDER_ENABLED
|
||||
}
|
83
tests/test_class_sh_disowning.py
Normal file
83
tests/test_class_sh_disowning.py
Normal file
@ -0,0 +1,83 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
from pybind11_tests import class_sh_disowning as m
|
||||
|
||||
if not m.defined_PYBIND11_SMART_HOLDER_ENABLED:
|
||||
pytest.skip("smart_holder not available.", allow_module_level=True)
|
||||
|
||||
|
||||
def is_disowned(obj):
|
||||
try:
|
||||
obj.get()
|
||||
except ValueError:
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def test_same_twice():
|
||||
while True:
|
||||
obj1a = m.Atype1(57)
|
||||
obj1b = m.Atype1(62)
|
||||
assert m.same_twice(obj1a, obj1b) == (57 * 10 + 1) * 100 + (62 * 10 + 1) * 10
|
||||
assert is_disowned(obj1a)
|
||||
assert is_disowned(obj1b)
|
||||
obj1c = m.Atype1(0)
|
||||
with pytest.raises(ValueError):
|
||||
# Disowning works for one argument, but not both.
|
||||
m.same_twice(obj1c, obj1c)
|
||||
assert is_disowned(obj1c)
|
||||
return # Comment out for manual leak checking (use `top` command).
|
||||
|
||||
|
||||
def test_mixed():
|
||||
first_pass = True
|
||||
while True:
|
||||
obj1a = m.Atype1(90)
|
||||
obj2a = m.Atype2(25)
|
||||
assert m.mixed(obj1a, obj2a) == (90 * 10 + 1) * 200 + (25 * 10 + 2) * 20
|
||||
assert is_disowned(obj1a)
|
||||
assert is_disowned(obj2a)
|
||||
|
||||
# The C++ order of evaluation of function arguments is (unfortunately) unspecified:
|
||||
# https://en.cppreference.com/w/cpp/language/eval_order
|
||||
# Read on.
|
||||
obj1b = m.Atype1(0)
|
||||
with pytest.raises(ValueError):
|
||||
# If the 1st argument is evaluated first, obj1b is disowned before the conversion for
|
||||
# the already disowned obj2a fails as expected.
|
||||
m.mixed(obj1b, obj2a)
|
||||
obj2b = m.Atype2(0)
|
||||
with pytest.raises(ValueError):
|
||||
# If the 2nd argument is evaluated first, obj2b is disowned before the conversion for
|
||||
# the already disowned obj1a fails as expected.
|
||||
m.mixed(obj1a, obj2b)
|
||||
|
||||
# Either obj1b or obj2b was disowned in the expected failed m.mixed() calls above, but not
|
||||
# both.
|
||||
is_disowned_results = (is_disowned(obj1b), is_disowned(obj2b))
|
||||
assert is_disowned_results.count(True) == 1
|
||||
if first_pass:
|
||||
first_pass = False
|
||||
print(
|
||||
"\nC++ function argument %d is evaluated first."
|
||||
% (is_disowned_results.index(True) + 1)
|
||||
)
|
||||
|
||||
return # Comment out for manual leak checking (use `top` command).
|
||||
|
||||
|
||||
def test_overloaded():
|
||||
while True:
|
||||
obj1 = m.Atype1(81)
|
||||
obj2 = m.Atype2(60)
|
||||
with pytest.raises(TypeError):
|
||||
m.overloaded(obj1, "NotInt")
|
||||
assert obj1.get() == 81 * 10 + 1 # Not disowned.
|
||||
assert m.overloaded(obj1, 3) == (81 * 10 + 1) * 30 + 3
|
||||
with pytest.raises(TypeError):
|
||||
m.overloaded(obj2, "NotInt")
|
||||
assert obj2.get() == 60 * 10 + 2 # Not disowned.
|
||||
assert m.overloaded(obj2, 2) == (60 * 10 + 2) * 40 + 2
|
||||
return # Comment out for manual leak checking (use `top` command).
|
102
tests/test_class_sh_disowning_mi.cpp
Normal file
102
tests/test_class_sh_disowning_mi.cpp
Normal file
@ -0,0 +1,102 @@
|
||||
#include <pybind11/smart_holder.h>
|
||||
|
||||
#include "pybind11_tests.h"
|
||||
|
||||
#include <memory>
|
||||
|
||||
namespace pybind11_tests {
|
||||
namespace class_sh_disowning_mi {
|
||||
|
||||
// Diamond inheritance (copied from test_multiple_inheritance.cpp).
|
||||
struct B {
|
||||
int val_b = 10;
|
||||
B() = default;
|
||||
B(const B &) = default;
|
||||
virtual ~B() = default;
|
||||
};
|
||||
|
||||
struct C0 : public virtual B {
|
||||
int val_c0 = 20;
|
||||
};
|
||||
|
||||
struct C1 : public virtual B {
|
||||
int val_c1 = 21;
|
||||
};
|
||||
|
||||
struct D : public C0, public C1 {
|
||||
int val_d = 30;
|
||||
};
|
||||
|
||||
void disown_b(std::unique_ptr<B>) {}
|
||||
|
||||
// test_multiple_inheritance_python
|
||||
struct Base1 {
|
||||
explicit Base1(int i) : i(i) {}
|
||||
int foo() const { return i; }
|
||||
int i;
|
||||
};
|
||||
|
||||
struct Base2 {
|
||||
explicit Base2(int j) : j(j) {}
|
||||
int bar() const { return j; }
|
||||
int j;
|
||||
};
|
||||
|
||||
int disown_base1(std::unique_ptr<Base1> b1) { return b1->i * 2000 + 1; }
|
||||
int disown_base2(std::unique_ptr<Base2> b2) { return b2->j * 2000 + 2; }
|
||||
|
||||
} // namespace class_sh_disowning_mi
|
||||
} // namespace pybind11_tests
|
||||
|
||||
PYBIND11_SMART_HOLDER_TYPE_CASTERS(pybind11_tests::class_sh_disowning_mi::B)
|
||||
PYBIND11_SMART_HOLDER_TYPE_CASTERS(pybind11_tests::class_sh_disowning_mi::C0)
|
||||
PYBIND11_SMART_HOLDER_TYPE_CASTERS(pybind11_tests::class_sh_disowning_mi::C1)
|
||||
PYBIND11_SMART_HOLDER_TYPE_CASTERS(pybind11_tests::class_sh_disowning_mi::D)
|
||||
|
||||
PYBIND11_SMART_HOLDER_TYPE_CASTERS(pybind11_tests::class_sh_disowning_mi::Base1)
|
||||
PYBIND11_SMART_HOLDER_TYPE_CASTERS(pybind11_tests::class_sh_disowning_mi::Base2)
|
||||
|
||||
TEST_SUBMODULE(class_sh_disowning_mi, m) {
|
||||
m.attr("defined_PYBIND11_SMART_HOLDER_ENABLED") =
|
||||
#ifndef PYBIND11_SMART_HOLDER_ENABLED
|
||||
false;
|
||||
#else
|
||||
true;
|
||||
|
||||
using namespace pybind11_tests::class_sh_disowning_mi;
|
||||
|
||||
py::classh<B>(m, "B")
|
||||
.def(py::init<>())
|
||||
.def_readonly("val_b", &D::val_b)
|
||||
.def("b", [](B *self) { return self; })
|
||||
.def("get", [](const B &self) { return self.val_b; });
|
||||
|
||||
py::classh<C0, B>(m, "C0")
|
||||
.def(py::init<>())
|
||||
.def_readonly("val_c0", &D::val_c0)
|
||||
.def("c0", [](C0 *self) { return self; })
|
||||
.def("get", [](const C0 &self) { return self.val_b * 100 + self.val_c0; });
|
||||
|
||||
py::classh<C1, B>(m, "C1")
|
||||
.def(py::init<>())
|
||||
.def_readonly("val_c1", &D::val_c1)
|
||||
.def("c1", [](C1 *self) { return self; })
|
||||
.def("get", [](const C1 &self) { return self.val_b * 100 + self.val_c1; });
|
||||
|
||||
py::classh<D, C0, C1>(m, "D")
|
||||
.def(py::init<>())
|
||||
.def_readonly("val_d", &D::val_d)
|
||||
.def("d", [](D *self) { return self; })
|
||||
.def("get", [](const D &self) {
|
||||
return self.val_b * 1000000 + self.val_c0 * 10000 + self.val_c1 * 100 + self.val_d;
|
||||
});
|
||||
|
||||
m.def("disown_b", disown_b);
|
||||
|
||||
// test_multiple_inheritance_python
|
||||
py::classh<Base1>(m, "Base1").def(py::init<int>()).def("foo", &Base1::foo);
|
||||
py::classh<Base2>(m, "Base2").def(py::init<int>()).def("bar", &Base2::bar);
|
||||
m.def("disown_base1", disown_base1);
|
||||
m.def("disown_base2", disown_base2);
|
||||
#endif // PYBIND11_SMART_HOLDER_ENABLED
|
||||
}
|
249
tests/test_class_sh_disowning_mi.py
Normal file
249
tests/test_class_sh_disowning_mi.py
Normal file
@ -0,0 +1,249 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
import env # noqa: F401
|
||||
from pybind11_tests import class_sh_disowning_mi as m
|
||||
|
||||
if not m.defined_PYBIND11_SMART_HOLDER_ENABLED:
|
||||
pytest.skip("smart_holder not available.", allow_module_level=True)
|
||||
|
||||
|
||||
def test_diamond_inheritance():
|
||||
# Very similar to test_multiple_inheritance.py:test_diamond_inheritance.
|
||||
d = m.D()
|
||||
assert d is d.d()
|
||||
assert d is d.c0()
|
||||
assert d is d.c1()
|
||||
assert d is d.b()
|
||||
assert d is d.c0().b()
|
||||
assert d is d.c1().b()
|
||||
assert d is d.c0().c1().b().c0().b()
|
||||
|
||||
|
||||
def is_disowned(callable_method):
|
||||
try:
|
||||
callable_method()
|
||||
except ValueError as e:
|
||||
assert "Python instance was disowned" in str(e) # noqa: PT017
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def test_disown_b():
|
||||
b = m.B()
|
||||
assert b.get() == 10
|
||||
m.disown_b(b)
|
||||
assert is_disowned(b.get)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("var_to_disown", ["c0", "b"])
|
||||
def test_disown_c0(var_to_disown):
|
||||
c0 = m.C0()
|
||||
assert c0.get() == 1020
|
||||
b = c0.b()
|
||||
m.disown_b(locals()[var_to_disown])
|
||||
assert is_disowned(c0.get)
|
||||
assert is_disowned(b.get)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("var_to_disown", ["c1", "b"])
|
||||
def test_disown_c1(var_to_disown):
|
||||
c1 = m.C1()
|
||||
assert c1.get() == 1021
|
||||
b = c1.b()
|
||||
m.disown_b(locals()[var_to_disown])
|
||||
assert is_disowned(c1.get)
|
||||
assert is_disowned(b.get)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("var_to_disown", ["d", "c1", "c0", "b"])
|
||||
def test_disown_d(var_to_disown):
|
||||
d = m.D()
|
||||
assert d.get() == 10202130
|
||||
b = d.b()
|
||||
c0 = d.c0()
|
||||
c1 = d.c1()
|
||||
m.disown_b(locals()[var_to_disown])
|
||||
assert is_disowned(d.get)
|
||||
assert is_disowned(c1.get)
|
||||
assert is_disowned(c0.get)
|
||||
assert is_disowned(b.get)
|
||||
|
||||
|
||||
# Based on test_multiple_inheritance.py:test_multiple_inheritance_python.
|
||||
class MI1(m.Base1, m.Base2):
|
||||
def __init__(self, i, j):
|
||||
m.Base1.__init__(self, i)
|
||||
m.Base2.__init__(self, j)
|
||||
|
||||
|
||||
class B1:
|
||||
def v(self):
|
||||
return 1
|
||||
|
||||
|
||||
class MI2(B1, m.Base1, m.Base2):
|
||||
def __init__(self, i, j):
|
||||
B1.__init__(self)
|
||||
m.Base1.__init__(self, i)
|
||||
m.Base2.__init__(self, j)
|
||||
|
||||
|
||||
class MI3(MI2):
|
||||
def __init__(self, i, j):
|
||||
MI2.__init__(self, i, j)
|
||||
|
||||
|
||||
class MI4(MI3, m.Base2):
|
||||
def __init__(self, i, j):
|
||||
MI3.__init__(self, i, j)
|
||||
# This should be ignored (Base2 is already initialized via MI2):
|
||||
m.Base2.__init__(self, i + 100)
|
||||
|
||||
|
||||
class MI5(m.Base2, B1, m.Base1):
|
||||
def __init__(self, i, j):
|
||||
B1.__init__(self)
|
||||
m.Base1.__init__(self, i)
|
||||
m.Base2.__init__(self, j)
|
||||
|
||||
|
||||
class MI6(m.Base2, B1):
|
||||
def __init__(self, i):
|
||||
m.Base2.__init__(self, i)
|
||||
B1.__init__(self)
|
||||
|
||||
|
||||
class B2(B1):
|
||||
def v(self):
|
||||
return 2
|
||||
|
||||
|
||||
class B3:
|
||||
def v(self):
|
||||
return 3
|
||||
|
||||
|
||||
class B4(B3, B2):
|
||||
def v(self):
|
||||
return 4
|
||||
|
||||
|
||||
class MI7(B4, MI6):
|
||||
def __init__(self, i):
|
||||
B4.__init__(self)
|
||||
MI6.__init__(self, i)
|
||||
|
||||
|
||||
class MI8(MI6, B3):
|
||||
def __init__(self, i):
|
||||
MI6.__init__(self, i)
|
||||
B3.__init__(self)
|
||||
|
||||
|
||||
class MI8b(B3, MI6):
|
||||
def __init__(self, i):
|
||||
B3.__init__(self)
|
||||
MI6.__init__(self, i)
|
||||
|
||||
|
||||
@pytest.mark.xfail("env.PYPY")
|
||||
def test_multiple_inheritance_python():
|
||||
# Based on test_multiple_inheritance.py:test_multiple_inheritance_python.
|
||||
# Exercises values_and_holders with 2 value_and_holder instances.
|
||||
|
||||
mi1 = MI1(1, 2)
|
||||
assert mi1.foo() == 1
|
||||
assert mi1.bar() == 2
|
||||
|
||||
mi2 = MI2(3, 4)
|
||||
assert mi2.v() == 1
|
||||
assert mi2.foo() == 3
|
||||
assert mi2.bar() == 4
|
||||
|
||||
mi3 = MI3(5, 6)
|
||||
assert mi3.v() == 1
|
||||
assert mi3.foo() == 5
|
||||
assert mi3.bar() == 6
|
||||
|
||||
mi4 = MI4(7, 8)
|
||||
assert mi4.v() == 1
|
||||
assert mi4.foo() == 7
|
||||
assert mi4.bar() == 8
|
||||
|
||||
mi5 = MI5(10, 11)
|
||||
assert mi5.v() == 1
|
||||
assert mi5.foo() == 10
|
||||
assert mi5.bar() == 11
|
||||
|
||||
mi6 = MI6(12)
|
||||
assert mi6.v() == 1
|
||||
assert mi6.bar() == 12
|
||||
|
||||
mi7 = MI7(13)
|
||||
assert mi7.v() == 4
|
||||
assert mi7.bar() == 13
|
||||
|
||||
mi8 = MI8(14)
|
||||
assert mi8.v() == 1
|
||||
assert mi8.bar() == 14
|
||||
|
||||
mi8b = MI8b(15)
|
||||
assert mi8b.v() == 3
|
||||
assert mi8b.bar() == 15
|
||||
|
||||
|
||||
DISOWN_CLS_I_J_V_LIST = [
|
||||
(MI1, 1, 2, None),
|
||||
(MI2, 3, 4, 1),
|
||||
(MI3, 5, 6, 1),
|
||||
(MI4, 7, 8, 1),
|
||||
(MI5, 10, 11, 1),
|
||||
]
|
||||
|
||||
|
||||
@pytest.mark.xfail("env.PYPY", strict=False)
|
||||
@pytest.mark.parametrize(("cls", "i", "j", "v"), DISOWN_CLS_I_J_V_LIST)
|
||||
def test_disown_base1_first(cls, i, j, v):
|
||||
obj = cls(i, j)
|
||||
assert obj.foo() == i
|
||||
assert m.disown_base1(obj) == 2000 * i + 1
|
||||
assert is_disowned(obj.foo)
|
||||
assert obj.bar() == j
|
||||
assert m.disown_base2(obj) == 2000 * j + 2
|
||||
assert is_disowned(obj.bar)
|
||||
if v is not None:
|
||||
assert obj.v() == v
|
||||
|
||||
|
||||
@pytest.mark.xfail("env.PYPY", strict=False)
|
||||
@pytest.mark.parametrize(("cls", "i", "j", "v"), DISOWN_CLS_I_J_V_LIST)
|
||||
def test_disown_base2_first(cls, i, j, v):
|
||||
obj = cls(i, j)
|
||||
assert obj.bar() == j
|
||||
assert m.disown_base2(obj) == 2000 * j + 2
|
||||
assert is_disowned(obj.bar)
|
||||
assert obj.foo() == i
|
||||
assert m.disown_base1(obj) == 2000 * i + 1
|
||||
assert is_disowned(obj.foo)
|
||||
if v is not None:
|
||||
assert obj.v() == v
|
||||
|
||||
|
||||
@pytest.mark.xfail("env.PYPY", strict=False)
|
||||
@pytest.mark.parametrize(
|
||||
("cls", "j", "v"),
|
||||
[
|
||||
(MI6, 12, 1),
|
||||
(MI7, 13, 4),
|
||||
(MI8, 14, 1),
|
||||
(MI8b, 15, 3),
|
||||
],
|
||||
)
|
||||
def test_disown_base2(cls, j, v):
|
||||
obj = cls(j)
|
||||
assert obj.bar() == j
|
||||
assert m.disown_base2(obj) == 2000 * j + 2
|
||||
assert is_disowned(obj.bar)
|
||||
assert obj.v() == v
|
187
tests/test_class_sh_factory_constructors.cpp
Normal file
187
tests/test_class_sh_factory_constructors.cpp
Normal file
@ -0,0 +1,187 @@
|
||||
#include <pybind11/smart_holder.h>
|
||||
|
||||
#include "pybind11_tests.h"
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
namespace pybind11_tests {
|
||||
namespace 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::make_shared<atyp_shmp>(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 class_sh_factory_constructors
|
||||
} // namespace pybind11_tests
|
||||
|
||||
PYBIND11_SMART_HOLDER_TYPE_CASTERS(pybind11_tests::class_sh_factory_constructors::atyp_valu)
|
||||
PYBIND11_SMART_HOLDER_TYPE_CASTERS(pybind11_tests::class_sh_factory_constructors::atyp_rref)
|
||||
PYBIND11_SMART_HOLDER_TYPE_CASTERS(pybind11_tests::class_sh_factory_constructors::atyp_cref)
|
||||
PYBIND11_SMART_HOLDER_TYPE_CASTERS(pybind11_tests::class_sh_factory_constructors::atyp_mref)
|
||||
PYBIND11_SMART_HOLDER_TYPE_CASTERS(pybind11_tests::class_sh_factory_constructors::atyp_cptr)
|
||||
PYBIND11_SMART_HOLDER_TYPE_CASTERS(pybind11_tests::class_sh_factory_constructors::atyp_mptr)
|
||||
PYBIND11_SMART_HOLDER_TYPE_CASTERS(pybind11_tests::class_sh_factory_constructors::atyp_shmp)
|
||||
PYBIND11_SMART_HOLDER_TYPE_CASTERS(pybind11_tests::class_sh_factory_constructors::atyp_shcp)
|
||||
PYBIND11_SMART_HOLDER_TYPE_CASTERS(pybind11_tests::class_sh_factory_constructors::atyp_uqmp)
|
||||
PYBIND11_SMART_HOLDER_TYPE_CASTERS(pybind11_tests::class_sh_factory_constructors::atyp_uqcp)
|
||||
PYBIND11_SMART_HOLDER_TYPE_CASTERS(pybind11_tests::class_sh_factory_constructors::atyp_udmp)
|
||||
PYBIND11_SMART_HOLDER_TYPE_CASTERS(pybind11_tests::class_sh_factory_constructors::atyp_udcp)
|
||||
PYBIND11_SMART_HOLDER_TYPE_CASTERS(pybind11_tests::class_sh_factory_constructors::with_alias)
|
||||
|
||||
TEST_SUBMODULE(class_sh_factory_constructors, m) {
|
||||
m.attr("defined_PYBIND11_SMART_HOLDER_ENABLED") =
|
||||
#ifndef PYBIND11_SMART_HOLDER_ENABLED
|
||||
false;
|
||||
#else
|
||||
true;
|
||||
|
||||
using namespace pybind11_tests::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::make_shared<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.
|
||||
}));
|
||||
#endif // PYBIND11_SMART_HOLDER_ENABLED
|
||||
}
|
56
tests/test_class_sh_factory_constructors.py
Normal file
56
tests/test_class_sh_factory_constructors.py
Normal file
@ -0,0 +1,56 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
from pybind11_tests import class_sh_factory_constructors as m
|
||||
|
||||
if not m.defined_PYBIND11_SMART_HOLDER_ENABLED:
|
||||
pytest.skip("smart_holder not available.", allow_module_level=True)
|
||||
|
||||
|
||||
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"
|
||||
)
|
112
tests/test_class_sh_inheritance.cpp
Normal file
112
tests/test_class_sh_inheritance.cpp
Normal file
@ -0,0 +1,112 @@
|
||||
#include <pybind11/smart_holder.h>
|
||||
|
||||
#include "pybind11_tests.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 &&) noexcept = default;
|
||||
base_template &operator=(const base_template &) = default;
|
||||
base_template &operator=(base_template &&) noexcept = 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::make_shared<drvd>(); }
|
||||
inline std::shared_ptr<base> rtrn_shmp_drvd_up_cast() { return std::make_shared<drvd>(); }
|
||||
|
||||
inline int pass_shcp_base(const std::shared_ptr<base const>& b) { return b->id() + 21; }
|
||||
inline int pass_shcp_drvd(const 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) {
|
||||
m.attr("defined_PYBIND11_SMART_HOLDER_ENABLED") =
|
||||
#ifndef PYBIND11_SMART_HOLDER_ENABLED
|
||||
false;
|
||||
#else
|
||||
true;
|
||||
|
||||
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);
|
||||
#endif // PYBIND11_SMART_HOLDER_ENABLED
|
||||
}
|
||||
|
||||
} // namespace class_sh_inheritance
|
||||
} // namespace pybind11_tests
|
68
tests/test_class_sh_inheritance.py
Normal file
68
tests/test_class_sh_inheritance.py
Normal file
@ -0,0 +1,68 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
from pybind11_tests import class_sh_inheritance as m
|
||||
|
||||
if not m.defined_PYBIND11_SMART_HOLDER_ENABLED:
|
||||
pytest.skip("smart_holder not available.", allow_module_level=True)
|
||||
|
||||
|
||||
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
|
107
tests/test_class_sh_mi_thunks.cpp
Normal file
107
tests/test_class_sh_mi_thunks.cpp
Normal file
@ -0,0 +1,107 @@
|
||||
#include <pybind11/pybind11.h>
|
||||
#include <pybind11/smart_holder.h>
|
||||
|
||||
#include "pybind11_tests.h"
|
||||
|
||||
#include <cstddef>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
namespace test_class_sh_mi_thunks {
|
||||
|
||||
// For general background: https://shaharmike.com/cpp/vtable-part2/
|
||||
// C++ vtables - Part 2 - Multiple Inheritance
|
||||
// ... the compiler creates a 'thunk' method that corrects `this` ...
|
||||
|
||||
struct Base0 {
|
||||
virtual ~Base0() = default;
|
||||
Base0() = default;
|
||||
Base0(const Base0 &) = delete;
|
||||
};
|
||||
|
||||
struct Base1 {
|
||||
virtual ~Base1() = default;
|
||||
// Using `vector` here because it is known to make this test very sensitive to bugs.
|
||||
std::vector<int> vec = {1, 2, 3, 4, 5};
|
||||
Base1() = default;
|
||||
Base1(const Base1 &) = delete;
|
||||
};
|
||||
|
||||
struct Derived : Base1, Base0 {
|
||||
~Derived() override = default;
|
||||
Derived() = default;
|
||||
Derived(const Derived &) = delete;
|
||||
};
|
||||
|
||||
} // namespace test_class_sh_mi_thunks
|
||||
|
||||
PYBIND11_SMART_HOLDER_TYPE_CASTERS(test_class_sh_mi_thunks::Base0)
|
||||
PYBIND11_SMART_HOLDER_TYPE_CASTERS(test_class_sh_mi_thunks::Base1)
|
||||
PYBIND11_SMART_HOLDER_TYPE_CASTERS(test_class_sh_mi_thunks::Derived)
|
||||
|
||||
TEST_SUBMODULE(class_sh_mi_thunks, m) {
|
||||
m.attr("defined_PYBIND11_SMART_HOLDER_ENABLED") =
|
||||
#ifndef PYBIND11_SMART_HOLDER_ENABLED
|
||||
false;
|
||||
#else
|
||||
true;
|
||||
|
||||
using namespace test_class_sh_mi_thunks;
|
||||
|
||||
m.def("ptrdiff_drvd_base0", []() {
|
||||
auto drvd = std::unique_ptr<Derived>(new Derived);
|
||||
auto *base0 = dynamic_cast<Base0 *>(drvd.get());
|
||||
return std::ptrdiff_t(reinterpret_cast<char *>(drvd.get())
|
||||
- reinterpret_cast<char *>(base0));
|
||||
});
|
||||
|
||||
py::classh<Base0>(m, "Base0");
|
||||
py::classh<Base1>(m, "Base1");
|
||||
py::classh<Derived, Base1, Base0>(m, "Derived");
|
||||
|
||||
m.def(
|
||||
"get_drvd_as_base0_raw_ptr",
|
||||
[]() {
|
||||
auto *drvd = new Derived;
|
||||
auto *base0 = dynamic_cast<Base0 *>(drvd);
|
||||
return base0;
|
||||
},
|
||||
py::return_value_policy::take_ownership);
|
||||
|
||||
m.def("get_drvd_as_base0_shared_ptr", []() {
|
||||
auto drvd = std::make_shared<Derived>();
|
||||
auto base0 = std::dynamic_pointer_cast<Base0>(drvd);
|
||||
return base0;
|
||||
});
|
||||
|
||||
m.def("get_drvd_as_base0_unique_ptr", []() {
|
||||
auto drvd = std::unique_ptr<Derived>(new Derived);
|
||||
auto base0 = std::unique_ptr<Base0>(std::move(drvd));
|
||||
return base0;
|
||||
});
|
||||
|
||||
m.def("vec_size_base0_raw_ptr", [](const Base0 *obj) {
|
||||
const auto *obj_der = dynamic_cast<const Derived *>(obj);
|
||||
if (obj_der == nullptr) {
|
||||
return std::size_t(0);
|
||||
}
|
||||
return obj_der->vec.size();
|
||||
});
|
||||
|
||||
m.def("vec_size_base0_shared_ptr", [](const std::shared_ptr<Base0> &obj) -> std::size_t {
|
||||
const auto obj_der = std::dynamic_pointer_cast<Derived>(obj);
|
||||
if (!obj_der) {
|
||||
return std::size_t(0);
|
||||
}
|
||||
return obj_der->vec.size();
|
||||
});
|
||||
|
||||
m.def("vec_size_base0_unique_ptr", [](std::unique_ptr<Base0> obj) -> std::size_t {
|
||||
const auto *obj_der = dynamic_cast<const Derived *>(obj.get());
|
||||
if (obj_der == nullptr) {
|
||||
return std::size_t(0);
|
||||
}
|
||||
return obj_der->vec.size();
|
||||
});
|
||||
#endif // PYBIND11_SMART_HOLDER_ENABLED
|
||||
}
|
56
tests/test_class_sh_mi_thunks.py
Normal file
56
tests/test_class_sh_mi_thunks.py
Normal file
@ -0,0 +1,56 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
from pybind11_tests import class_sh_mi_thunks as m
|
||||
|
||||
if not m.defined_PYBIND11_SMART_HOLDER_ENABLED:
|
||||
pytest.skip("smart_holder not available.", allow_module_level=True)
|
||||
|
||||
|
||||
def test_ptrdiff_drvd_base0():
|
||||
ptrdiff = m.ptrdiff_drvd_base0()
|
||||
# A failure here does not (necessarily) mean that there is a bug, but that
|
||||
# test_class_sh_mi_thunks is not exercising what it is supposed to.
|
||||
# If this ever fails on some platforms: use pytest.skip()
|
||||
# If this ever fails on all platforms: don't know, seems extremely unlikely.
|
||||
assert ptrdiff != 0
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"vec_size_fn",
|
||||
[
|
||||
m.vec_size_base0_raw_ptr,
|
||||
m.vec_size_base0_shared_ptr,
|
||||
],
|
||||
)
|
||||
@pytest.mark.parametrize(
|
||||
"get_fn",
|
||||
[
|
||||
m.get_drvd_as_base0_raw_ptr,
|
||||
m.get_drvd_as_base0_shared_ptr,
|
||||
m.get_drvd_as_base0_unique_ptr,
|
||||
],
|
||||
)
|
||||
def test_get_vec_size_raw_shared(get_fn, vec_size_fn):
|
||||
obj = get_fn()
|
||||
assert vec_size_fn(obj) == 5
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"get_fn", [m.get_drvd_as_base0_raw_ptr, m.get_drvd_as_base0_unique_ptr]
|
||||
)
|
||||
def test_get_vec_size_unique(get_fn):
|
||||
obj = get_fn()
|
||||
assert m.vec_size_base0_unique_ptr(obj) == 5
|
||||
with pytest.raises(ValueError, match="Python instance was disowned"):
|
||||
m.vec_size_base0_unique_ptr(obj)
|
||||
|
||||
|
||||
def test_get_shared_vec_size_unique():
|
||||
obj = m.get_drvd_as_base0_shared_ptr()
|
||||
with pytest.raises(ValueError) as exc_info:
|
||||
m.vec_size_base0_unique_ptr(obj)
|
||||
assert (
|
||||
str(exc_info.value) == "Cannot disown external shared_ptr (load_as_unique_ptr)."
|
||||
)
|
113
tests/test_class_sh_property.cpp
Normal file
113
tests/test_class_sh_property.cpp
Normal file
@ -0,0 +1,113 @@
|
||||
// The compact 4-character naming matches that in test_class_sh_basic.cpp
|
||||
// Variable names are intentionally terse, to not distract from the more important C++ type names:
|
||||
// valu(e), ref(erence), ptr or p (pointer), r = rvalue, m = mutable, c = const,
|
||||
// sh = shared_ptr, uq = unique_ptr.
|
||||
|
||||
#include "pybind11/smart_holder.h"
|
||||
#include "pybind11_tests.h"
|
||||
|
||||
#include <memory>
|
||||
|
||||
namespace test_class_sh_property {
|
||||
|
||||
struct ClassicField {
|
||||
int num = -88;
|
||||
};
|
||||
|
||||
struct ClassicOuter {
|
||||
ClassicField *m_mptr = nullptr;
|
||||
const ClassicField *m_cptr = nullptr;
|
||||
};
|
||||
|
||||
struct Field {
|
||||
int num = -99;
|
||||
};
|
||||
|
||||
struct Outer {
|
||||
Field m_valu;
|
||||
Field *m_mptr = nullptr;
|
||||
const Field *m_cptr = nullptr;
|
||||
std::unique_ptr<Field> m_uqmp;
|
||||
std::unique_ptr<const Field> m_uqcp;
|
||||
std::shared_ptr<Field> m_shmp;
|
||||
std::shared_ptr<const Field> m_shcp;
|
||||
};
|
||||
|
||||
inline void DisownOuter(std::unique_ptr<Outer>) {}
|
||||
|
||||
struct WithCharArrayMember {
|
||||
WithCharArrayMember() { std::memcpy(char6_member, "Char6", 6); }
|
||||
char char6_member[6];
|
||||
};
|
||||
|
||||
struct WithConstCharPtrMember {
|
||||
const char *const_char_ptr_member = "ConstChar*";
|
||||
};
|
||||
|
||||
} // namespace test_class_sh_property
|
||||
|
||||
PYBIND11_TYPE_CASTER_BASE_HOLDER(test_class_sh_property::ClassicField,
|
||||
std::unique_ptr<test_class_sh_property::ClassicField>)
|
||||
PYBIND11_TYPE_CASTER_BASE_HOLDER(test_class_sh_property::ClassicOuter,
|
||||
std::unique_ptr<test_class_sh_property::ClassicOuter>)
|
||||
|
||||
PYBIND11_SMART_HOLDER_TYPE_CASTERS(test_class_sh_property::Field)
|
||||
PYBIND11_SMART_HOLDER_TYPE_CASTERS(test_class_sh_property::Outer)
|
||||
|
||||
PYBIND11_SMART_HOLDER_TYPE_CASTERS(test_class_sh_property::WithCharArrayMember)
|
||||
PYBIND11_SMART_HOLDER_TYPE_CASTERS(test_class_sh_property::WithConstCharPtrMember)
|
||||
|
||||
TEST_SUBMODULE(class_sh_property, m) {
|
||||
m.attr("defined_PYBIND11_SMART_HOLDER_ENABLED") =
|
||||
#ifndef PYBIND11_SMART_HOLDER_ENABLED
|
||||
false;
|
||||
#else
|
||||
true;
|
||||
|
||||
using namespace test_class_sh_property;
|
||||
|
||||
py::class_<ClassicField, std::unique_ptr<ClassicField>>(m, "ClassicField")
|
||||
.def(py::init<>())
|
||||
.def_readwrite("num", &ClassicField::num);
|
||||
|
||||
py::class_<ClassicOuter, std::unique_ptr<ClassicOuter>>(m, "ClassicOuter")
|
||||
.def(py::init<>())
|
||||
.def_readonly("m_mptr_readonly", &ClassicOuter::m_mptr)
|
||||
.def_readwrite("m_mptr_readwrite", &ClassicOuter::m_mptr)
|
||||
.def_readwrite("m_cptr_readonly", &ClassicOuter::m_cptr)
|
||||
.def_readwrite("m_cptr_readwrite", &ClassicOuter::m_cptr);
|
||||
|
||||
py::classh<Field>(m, "Field").def(py::init<>()).def_readwrite("num", &Field::num);
|
||||
|
||||
py::classh<Outer>(m, "Outer")
|
||||
.def(py::init<>())
|
||||
|
||||
.def_readonly("m_valu_readonly", &Outer::m_valu)
|
||||
.def_readwrite("m_valu_readwrite", &Outer::m_valu)
|
||||
|
||||
.def_readonly("m_mptr_readonly", &Outer::m_mptr)
|
||||
.def_readwrite("m_mptr_readwrite", &Outer::m_mptr)
|
||||
.def_readonly("m_cptr_readonly", &Outer::m_cptr)
|
||||
.def_readwrite("m_cptr_readwrite", &Outer::m_cptr)
|
||||
|
||||
// .def_readonly("m_uqmp_readonly", &Outer::m_uqmp) // Custom compilation Error.
|
||||
.def_readwrite("m_uqmp_readwrite", &Outer::m_uqmp)
|
||||
// .def_readonly("m_uqcp_readonly", &Outer::m_uqcp) // Custom compilation Error.
|
||||
.def_readwrite("m_uqcp_readwrite", &Outer::m_uqcp)
|
||||
|
||||
.def_readwrite("m_shmp_readonly", &Outer::m_shmp)
|
||||
.def_readwrite("m_shmp_readwrite", &Outer::m_shmp)
|
||||
.def_readwrite("m_shcp_readonly", &Outer::m_shcp)
|
||||
.def_readwrite("m_shcp_readwrite", &Outer::m_shcp);
|
||||
|
||||
m.def("DisownOuter", DisownOuter);
|
||||
|
||||
py::classh<WithCharArrayMember>(m, "WithCharArrayMember")
|
||||
.def(py::init<>())
|
||||
.def_readonly("char6_member", &WithCharArrayMember::char6_member);
|
||||
|
||||
py::classh<WithConstCharPtrMember>(m, "WithConstCharPtrMember")
|
||||
.def(py::init<>())
|
||||
.def_readonly("const_char_ptr_member", &WithConstCharPtrMember::const_char_ptr_member);
|
||||
#endif // PYBIND11_SMART_HOLDER_ENABLED
|
||||
}
|
167
tests/test_class_sh_property.py
Normal file
167
tests/test_class_sh_property.py
Normal file
@ -0,0 +1,167 @@
|
||||
# The compact 4-character naming scheme (e.g. mptr, cptr, shcp) is explained at the top of
|
||||
# test_class_sh_property.cpp.
|
||||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
import env # noqa: F401
|
||||
from pybind11_tests import class_sh_property as m
|
||||
|
||||
if not m.defined_PYBIND11_SMART_HOLDER_ENABLED:
|
||||
pytest.skip("smart_holder not available.", allow_module_level=True)
|
||||
|
||||
|
||||
@pytest.mark.xfail("env.PYPY", reason="gc after `del field` is apparently deferred")
|
||||
@pytest.mark.parametrize("m_attr", ["m_valu_readonly", "m_valu_readwrite"])
|
||||
def test_valu_getter(m_attr):
|
||||
# Reduced from PyCLIF test:
|
||||
# https://github.com/google/clif/blob/c371a6d4b28d25d53a16e6d2a6d97305fb1be25a/clif/testing/python/nested_fields_test.py#L56
|
||||
outer = m.Outer()
|
||||
field = getattr(outer, m_attr)
|
||||
assert field.num == -99
|
||||
with pytest.raises(ValueError) as excinfo:
|
||||
m.DisownOuter(outer)
|
||||
assert str(excinfo.value) == "Cannot disown use_count != 1 (load_as_unique_ptr)."
|
||||
del field
|
||||
m.DisownOuter(outer)
|
||||
with pytest.raises(ValueError, match="Python instance was disowned") as excinfo:
|
||||
getattr(outer, m_attr)
|
||||
|
||||
|
||||
def test_valu_setter():
|
||||
outer = m.Outer()
|
||||
assert outer.m_valu_readonly.num == -99
|
||||
assert outer.m_valu_readwrite.num == -99
|
||||
field = m.Field()
|
||||
field.num = 35
|
||||
outer.m_valu_readwrite = field
|
||||
assert outer.m_valu_readonly.num == 35
|
||||
assert outer.m_valu_readwrite.num == 35
|
||||
|
||||
|
||||
@pytest.mark.parametrize("m_attr", ["m_shmp", "m_shcp"])
|
||||
def test_shp(m_attr):
|
||||
m_attr_readonly = m_attr + "_readonly"
|
||||
m_attr_readwrite = m_attr + "_readwrite"
|
||||
outer = m.Outer()
|
||||
assert getattr(outer, m_attr_readonly) is None
|
||||
assert getattr(outer, m_attr_readwrite) is None
|
||||
field = m.Field()
|
||||
field.num = 43
|
||||
setattr(outer, m_attr_readwrite, field)
|
||||
assert getattr(outer, m_attr_readonly).num == 43
|
||||
assert getattr(outer, m_attr_readwrite).num == 43
|
||||
getattr(outer, m_attr_readonly).num = 57
|
||||
getattr(outer, m_attr_readwrite).num = 57
|
||||
assert field.num == 57
|
||||
del field
|
||||
assert getattr(outer, m_attr_readonly).num == 57
|
||||
assert getattr(outer, m_attr_readwrite).num == 57
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("field_type", "num_default", "outer_type"),
|
||||
[
|
||||
(m.ClassicField, -88, m.ClassicOuter),
|
||||
(m.Field, -99, m.Outer),
|
||||
],
|
||||
)
|
||||
@pytest.mark.parametrize("m_attr", ["m_mptr", "m_cptr"])
|
||||
@pytest.mark.parametrize("r_kind", ["_readonly", "_readwrite"])
|
||||
def test_ptr(field_type, num_default, outer_type, m_attr, r_kind):
|
||||
m_attr_r_kind = m_attr + r_kind
|
||||
outer = outer_type()
|
||||
assert getattr(outer, m_attr_r_kind) is None
|
||||
field = field_type()
|
||||
assert field.num == num_default
|
||||
setattr(outer, m_attr + "_readwrite", field)
|
||||
assert getattr(outer, m_attr_r_kind).num == num_default
|
||||
field.num = 76
|
||||
assert getattr(outer, m_attr_r_kind).num == 76
|
||||
# Change to -88 or -99 to demonstrate Undefined Behavior (dangling pointer).
|
||||
if num_default == 88 and m_attr == "m_mptr":
|
||||
del field
|
||||
assert getattr(outer, m_attr_r_kind).num == 76
|
||||
|
||||
|
||||
@pytest.mark.parametrize("m_attr_readwrite", ["m_uqmp_readwrite", "m_uqcp_readwrite"])
|
||||
def test_uqp(m_attr_readwrite):
|
||||
outer = m.Outer()
|
||||
assert getattr(outer, m_attr_readwrite) is None
|
||||
field_orig = m.Field()
|
||||
field_orig.num = 39
|
||||
setattr(outer, m_attr_readwrite, field_orig)
|
||||
with pytest.raises(ValueError, match="Python instance was disowned"):
|
||||
_ = field_orig.num
|
||||
field_retr1 = getattr(outer, m_attr_readwrite)
|
||||
assert getattr(outer, m_attr_readwrite) is None
|
||||
assert field_retr1.num == 39
|
||||
field_retr1.num = 93
|
||||
setattr(outer, m_attr_readwrite, field_retr1)
|
||||
with pytest.raises(ValueError):
|
||||
_ = field_retr1.num
|
||||
field_retr2 = getattr(outer, m_attr_readwrite)
|
||||
assert field_retr2.num == 93
|
||||
|
||||
|
||||
# Proof-of-concept (POC) for safe & intuitive Python access to unique_ptr members.
|
||||
# The C++ member unique_ptr is disowned to a temporary Python object for accessing
|
||||
# an attribute of the member. After the attribute was accessed, the Python object
|
||||
# is disowned back to the C++ member unique_ptr.
|
||||
# Productizing this POC is left for a future separate PR, as needed.
|
||||
class unique_ptr_field_proxy_poc:
|
||||
def __init__(self, obj, field_name):
|
||||
object.__setattr__(self, "__obj", obj)
|
||||
object.__setattr__(self, "__field_name", field_name)
|
||||
|
||||
def __getattr__(self, *args, **kwargs):
|
||||
return _proxy_dereference(self, getattr, *args, **kwargs)
|
||||
|
||||
def __setattr__(self, *args, **kwargs):
|
||||
return _proxy_dereference(self, setattr, *args, **kwargs)
|
||||
|
||||
def __delattr__(self, *args, **kwargs):
|
||||
return _proxy_dereference(self, delattr, *args, **kwargs)
|
||||
|
||||
|
||||
def _proxy_dereference(proxy, xxxattr, *args, **kwargs):
|
||||
obj = object.__getattribute__(proxy, "__obj")
|
||||
field_name = object.__getattribute__(proxy, "__field_name")
|
||||
field = getattr(obj, field_name) # Disowns the C++ unique_ptr member.
|
||||
assert field is not None
|
||||
try:
|
||||
return xxxattr(field, *args, **kwargs)
|
||||
finally:
|
||||
setattr(obj, field_name, field) # Disowns the temporary Python object (field).
|
||||
|
||||
|
||||
@pytest.mark.parametrize("m_attr", ["m_uqmp", "m_uqcp"])
|
||||
def test_unique_ptr_field_proxy_poc(m_attr):
|
||||
m_attr_readwrite = m_attr + "_readwrite"
|
||||
outer = m.Outer()
|
||||
field_orig = m.Field()
|
||||
field_orig.num = 45
|
||||
setattr(outer, m_attr_readwrite, field_orig)
|
||||
field_proxy = unique_ptr_field_proxy_poc(outer, m_attr_readwrite)
|
||||
assert field_proxy.num == 45
|
||||
assert field_proxy.num == 45
|
||||
with pytest.raises(AttributeError):
|
||||
_ = field_proxy.xyz
|
||||
assert field_proxy.num == 45
|
||||
field_proxy.num = 82
|
||||
assert field_proxy.num == 82
|
||||
field_proxy = unique_ptr_field_proxy_poc(outer, m_attr_readwrite)
|
||||
assert field_proxy.num == 82
|
||||
with pytest.raises(AttributeError):
|
||||
del field_proxy.num
|
||||
assert field_proxy.num == 82
|
||||
|
||||
|
||||
def test_readonly_char6_member():
|
||||
obj = m.WithCharArrayMember()
|
||||
assert obj.char6_member == "Char6"
|
||||
|
||||
|
||||
def test_readonly_const_char_ptr_member():
|
||||
obj = m.WithConstCharPtrMember()
|
||||
assert obj.const_char_ptr_member == "ConstChar*"
|
75
tests/test_class_sh_property_non_owning.cpp
Normal file
75
tests/test_class_sh_property_non_owning.cpp
Normal file
@ -0,0 +1,75 @@
|
||||
#include "pybind11/smart_holder.h"
|
||||
#include "pybind11_tests.h"
|
||||
|
||||
#include <cstddef>
|
||||
#include <vector>
|
||||
|
||||
namespace test_class_sh_property_non_owning {
|
||||
|
||||
struct CoreField {
|
||||
explicit CoreField(int int_value = -99) : int_value{int_value} {}
|
||||
int int_value;
|
||||
};
|
||||
|
||||
struct DataField {
|
||||
DataField(int i_value, int i_shared, int i_unique)
|
||||
: core_fld_value{i_value}, core_fld_shared_ptr{new CoreField{i_shared}},
|
||||
core_fld_raw_ptr{core_fld_shared_ptr.get()},
|
||||
core_fld_unique_ptr{new CoreField{i_unique}} {}
|
||||
CoreField core_fld_value;
|
||||
std::shared_ptr<CoreField> core_fld_shared_ptr;
|
||||
CoreField *core_fld_raw_ptr;
|
||||
std::unique_ptr<CoreField> core_fld_unique_ptr;
|
||||
};
|
||||
|
||||
struct DataFieldsHolder {
|
||||
private:
|
||||
std::vector<DataField> vec;
|
||||
|
||||
public:
|
||||
explicit DataFieldsHolder(std::size_t vec_size) {
|
||||
for (std::size_t i = 0; i < vec_size; i++) {
|
||||
int i11 = static_cast<int>(i) * 11;
|
||||
vec.emplace_back(13 + i11, 14 + i11, 15 + i11);
|
||||
}
|
||||
}
|
||||
|
||||
DataField *vec_at(std::size_t index) {
|
||||
if (index >= vec.size()) {
|
||||
return nullptr;
|
||||
}
|
||||
return &vec[index];
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace test_class_sh_property_non_owning
|
||||
|
||||
using namespace test_class_sh_property_non_owning;
|
||||
|
||||
PYBIND11_SMART_HOLDER_TYPE_CASTERS(CoreField)
|
||||
PYBIND11_SMART_HOLDER_TYPE_CASTERS(DataField)
|
||||
PYBIND11_SMART_HOLDER_TYPE_CASTERS(DataFieldsHolder)
|
||||
|
||||
TEST_SUBMODULE(class_sh_property_non_owning, m) {
|
||||
m.attr("defined_PYBIND11_SMART_HOLDER_ENABLED") =
|
||||
#ifndef PYBIND11_SMART_HOLDER_ENABLED
|
||||
false;
|
||||
#else
|
||||
true;
|
||||
|
||||
py::classh<CoreField>(m, "CoreField").def_readwrite("int_value", &CoreField::int_value);
|
||||
|
||||
py::classh<DataField>(m, "DataField")
|
||||
.def_readonly("core_fld_value_ro", &DataField::core_fld_value)
|
||||
.def_readwrite("core_fld_value_rw", &DataField::core_fld_value)
|
||||
.def_readonly("core_fld_shared_ptr_ro", &DataField::core_fld_shared_ptr)
|
||||
.def_readwrite("core_fld_shared_ptr_rw", &DataField::core_fld_shared_ptr)
|
||||
.def_readonly("core_fld_raw_ptr_ro", &DataField::core_fld_raw_ptr)
|
||||
.def_readwrite("core_fld_raw_ptr_rw", &DataField::core_fld_raw_ptr)
|
||||
.def_readwrite("core_fld_unique_ptr_rw", &DataField::core_fld_unique_ptr);
|
||||
|
||||
py::classh<DataFieldsHolder>(m, "DataFieldsHolder")
|
||||
.def(py::init<std::size_t>())
|
||||
.def("vec_at", &DataFieldsHolder::vec_at, py::return_value_policy::reference_internal);
|
||||
#endif // PYBIND11_SMART_HOLDER_ENABLED
|
||||
}
|
33
tests/test_class_sh_property_non_owning.py
Normal file
33
tests/test_class_sh_property_non_owning.py
Normal file
@ -0,0 +1,33 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
from pybind11_tests import class_sh_property_non_owning as m
|
||||
|
||||
if not m.defined_PYBIND11_SMART_HOLDER_ENABLED:
|
||||
pytest.skip("smart_holder not available.", allow_module_level=True)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("persistent_holder", [True, False])
|
||||
@pytest.mark.parametrize(
|
||||
("core_fld", "expected"),
|
||||
[
|
||||
("core_fld_value_ro", (13, 24)),
|
||||
("core_fld_value_rw", (13, 24)),
|
||||
("core_fld_shared_ptr_ro", (14, 25)),
|
||||
("core_fld_shared_ptr_rw", (14, 25)),
|
||||
("core_fld_raw_ptr_ro", (14, 25)),
|
||||
("core_fld_raw_ptr_rw", (14, 25)),
|
||||
("core_fld_unique_ptr_rw", (15, 26)),
|
||||
],
|
||||
)
|
||||
def test_core_fld_common(core_fld, expected, persistent_holder):
|
||||
if persistent_holder:
|
||||
h = m.DataFieldsHolder(2)
|
||||
for i, exp in enumerate(expected):
|
||||
c = getattr(h.vec_at(i), core_fld)
|
||||
assert c.int_value == exp
|
||||
else:
|
||||
for i, exp in enumerate(expected):
|
||||
c = getattr(m.DataFieldsHolder(2).vec_at(i), core_fld)
|
||||
assert c.int_value == exp
|
119
tests/test_class_sh_shared_ptr_copy_move.cpp
Normal file
119
tests/test_class_sh_shared_ptr_copy_move.cpp
Normal file
@ -0,0 +1,119 @@
|
||||
#include <pybind11/smart_holder.h>
|
||||
|
||||
#include "pybind11_tests.h"
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace pybind11_tests {
|
||||
namespace {
|
||||
|
||||
const std::string fooNames[] = {"ShPtr_", "SmHld_"};
|
||||
|
||||
template <int SerNo>
|
||||
struct Foo {
|
||||
std::string history;
|
||||
explicit Foo(const std::string &history_) : history(history_) {}
|
||||
Foo(const Foo &other) : history(other.history + "_CpCtor") {}
|
||||
Foo(Foo &&other) noexcept : history(other.history + "_MvCtor") {}
|
||||
Foo &operator=(const Foo &other) {
|
||||
history = other.history + "_OpEqLv";
|
||||
return *this;
|
||||
}
|
||||
Foo &operator=(Foo &&other) noexcept {
|
||||
history = other.history + "_OpEqRv";
|
||||
return *this;
|
||||
}
|
||||
std::string get_history() const { return "Foo" + fooNames[SerNo] + history; }
|
||||
};
|
||||
|
||||
using FooShPtr = Foo<0>;
|
||||
using FooSmHld = Foo<1>;
|
||||
|
||||
struct Outer {
|
||||
std::shared_ptr<FooShPtr> ShPtr;
|
||||
std::shared_ptr<FooSmHld> SmHld;
|
||||
Outer()
|
||||
: ShPtr(std::make_shared<FooShPtr>("Outer")), SmHld(std::make_shared<FooSmHld>("Outer")) {}
|
||||
std::shared_ptr<FooShPtr> getShPtr() const { return ShPtr; }
|
||||
std::shared_ptr<FooSmHld> getSmHld() const { return SmHld; }
|
||||
};
|
||||
|
||||
} // namespace
|
||||
} // namespace pybind11_tests
|
||||
|
||||
PYBIND11_TYPE_CASTER_BASE_HOLDER(pybind11_tests::FooShPtr,
|
||||
std::shared_ptr<pybind11_tests::FooShPtr>)
|
||||
PYBIND11_SMART_HOLDER_TYPE_CASTERS(pybind11_tests::FooSmHld)
|
||||
|
||||
namespace pybind11_tests {
|
||||
|
||||
TEST_SUBMODULE(class_sh_shared_ptr_copy_move, m) {
|
||||
m.attr("defined_PYBIND11_SMART_HOLDER_ENABLED") =
|
||||
#ifndef PYBIND11_SMART_HOLDER_ENABLED
|
||||
false;
|
||||
#else
|
||||
true;
|
||||
|
||||
namespace py = pybind11;
|
||||
|
||||
py::class_<FooShPtr, std::shared_ptr<FooShPtr>>(m, "FooShPtr")
|
||||
.def("get_history", &FooShPtr::get_history);
|
||||
py::classh<FooSmHld>(m, "FooSmHld").def("get_history", &FooSmHld::get_history);
|
||||
|
||||
auto outer = py::class_<Outer>(m, "Outer").def(py::init());
|
||||
# define MAKE_PROP(PropTyp) \
|
||||
MAKE_PROP_FOO(ShPtr, PropTyp) \
|
||||
MAKE_PROP_FOO(SmHld, PropTyp)
|
||||
|
||||
# define MAKE_PROP_FOO(FooTyp, PropTyp) \
|
||||
.def_##PropTyp(#FooTyp "_" #PropTyp "_default", &Outer::FooTyp) \
|
||||
.def_##PropTyp( \
|
||||
#FooTyp "_" #PropTyp "_copy", &Outer::FooTyp, py::return_value_policy::copy) \
|
||||
.def_##PropTyp( \
|
||||
#FooTyp "_" #PropTyp "_move", &Outer::FooTyp, py::return_value_policy::move)
|
||||
outer MAKE_PROP(readonly) MAKE_PROP(readwrite);
|
||||
# undef MAKE_PROP_FOO
|
||||
|
||||
# define MAKE_PROP_FOO(FooTyp, PropTyp) \
|
||||
.def_##PropTyp(#FooTyp "_property_" #PropTyp "_default", &Outer::FooTyp) \
|
||||
.def_property_##PropTyp(#FooTyp "_property_" #PropTyp "_copy", \
|
||||
&Outer::get##FooTyp, \
|
||||
py::return_value_policy::copy) \
|
||||
.def_property_##PropTyp(#FooTyp "_property_" #PropTyp "_move", \
|
||||
&Outer::get##FooTyp, \
|
||||
py::return_value_policy::move)
|
||||
outer MAKE_PROP(readonly);
|
||||
# undef MAKE_PROP_FOO
|
||||
# undef MAKE_PROP
|
||||
|
||||
m.def("test_ShPtr_copy", []() {
|
||||
auto o = std::make_shared<FooShPtr>("copy");
|
||||
auto l = py::list();
|
||||
l.append(o);
|
||||
return l;
|
||||
});
|
||||
m.def("test_SmHld_copy", []() {
|
||||
auto o = std::make_shared<FooSmHld>("copy");
|
||||
auto l = py::list();
|
||||
l.append(o);
|
||||
return l;
|
||||
});
|
||||
|
||||
m.def("test_ShPtr_move", []() {
|
||||
auto o = std::make_shared<FooShPtr>("move");
|
||||
auto l = py::list();
|
||||
l.append(std::move(o));
|
||||
return l;
|
||||
});
|
||||
m.def("test_SmHld_move", []() {
|
||||
auto o = std::make_shared<FooSmHld>("move");
|
||||
auto l = py::list();
|
||||
l.append(std::move(o));
|
||||
return l;
|
||||
});
|
||||
#endif // PYBIND11_SMART_HOLDER_ENABLED
|
||||
}
|
||||
|
||||
} // namespace pybind11_tests
|
46
tests/test_class_sh_shared_ptr_copy_move.py
Normal file
46
tests/test_class_sh_shared_ptr_copy_move.py
Normal file
@ -0,0 +1,46 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
from pybind11_tests import class_sh_shared_ptr_copy_move as m
|
||||
|
||||
if not m.defined_PYBIND11_SMART_HOLDER_ENABLED:
|
||||
pytest.skip("smart_holder not available.", allow_module_level=True)
|
||||
|
||||
|
||||
def test_shptr_copy():
|
||||
txt = m.test_ShPtr_copy()[0].get_history()
|
||||
assert txt == "FooShPtr_copy"
|
||||
|
||||
|
||||
def test_smhld_copy():
|
||||
txt = m.test_SmHld_copy()[0].get_history()
|
||||
assert txt == "FooSmHld_copy"
|
||||
|
||||
|
||||
def test_shptr_move():
|
||||
txt = m.test_ShPtr_move()[0].get_history()
|
||||
assert txt == "FooShPtr_move"
|
||||
|
||||
|
||||
def test_smhld_move():
|
||||
txt = m.test_SmHld_move()[0].get_history()
|
||||
assert txt == "FooSmHld_move"
|
||||
|
||||
|
||||
def _check_property(foo_typ, prop_typ, policy):
|
||||
o = m.Outer()
|
||||
name = f"{foo_typ}_{prop_typ}_{policy}"
|
||||
history = f"Foo{foo_typ}_Outer"
|
||||
f = getattr(o, name)
|
||||
assert f.get_history() == history
|
||||
# and try again to check that o did not get changed
|
||||
f = getattr(o, name)
|
||||
assert f.get_history() == history
|
||||
|
||||
|
||||
def test_properties():
|
||||
for prop_typ in ("readonly", "readwrite", "property_readonly"):
|
||||
for foo_typ in ("ShPtr", "SmHld"):
|
||||
for policy in ("default", "copy", "move"):
|
||||
_check_property(foo_typ, prop_typ, policy)
|
98
tests/test_class_sh_trampoline_basic.cpp
Normal file
98
tests/test_class_sh_trampoline_basic.cpp
Normal file
@ -0,0 +1,98 @@
|
||||
#include <pybind11/smart_holder.h>
|
||||
|
||||
#include "pybind11_tests.h"
|
||||
|
||||
#include <memory>
|
||||
|
||||
namespace pybind11_tests {
|
||||
namespace class_sh_trampoline_basic {
|
||||
|
||||
template <int SerNo> // Using int as a trick to easily generate a series of types.
|
||||
struct Abase {
|
||||
int val = 0;
|
||||
virtual ~Abase() = default;
|
||||
explicit Abase(int val_) : val{val_} {}
|
||||
int Get() const { return val * 10 + 3; }
|
||||
virtual int Add(int other_val) const = 0;
|
||||
|
||||
// Some compilers complain about implicitly defined versions of some of the following:
|
||||
Abase(const Abase &) = default;
|
||||
Abase(Abase &&) noexcept = default;
|
||||
Abase &operator=(const Abase &) = default;
|
||||
Abase &operator=(Abase &&) noexcept = default;
|
||||
};
|
||||
|
||||
template <int SerNo>
|
||||
struct AbaseAlias : Abase<SerNo> {
|
||||
using Abase<SerNo>::Abase;
|
||||
|
||||
int Add(int other_val) const override {
|
||||
PYBIND11_OVERRIDE_PURE(int, /* Return type */
|
||||
Abase<SerNo>, /* Parent class */
|
||||
Add, /* Name of function in C++ (must match Python name) */
|
||||
other_val);
|
||||
}
|
||||
};
|
||||
|
||||
#ifdef PYBIND11_SMART_HOLDER_ENABLED
|
||||
template <>
|
||||
struct AbaseAlias<1> : Abase<1>, py::trampoline_self_life_support {
|
||||
using Abase<1>::Abase;
|
||||
|
||||
int Add(int other_val) const override {
|
||||
PYBIND11_OVERRIDE_PURE(int, /* Return type */
|
||||
Abase<1>, /* Parent class */
|
||||
Add, /* Name of function in C++ (must match Python name) */
|
||||
other_val);
|
||||
}
|
||||
};
|
||||
#endif // PYBIND11_SMART_HOLDER_ENABLED
|
||||
|
||||
template <int SerNo>
|
||||
int AddInCppRawPtr(const Abase<SerNo> *obj, int other_val) {
|
||||
return obj->Add(other_val) * 10 + 7;
|
||||
}
|
||||
|
||||
template <int SerNo>
|
||||
int AddInCppSharedPtr(std::shared_ptr<Abase<SerNo>> obj, int other_val) {
|
||||
return obj->Add(other_val) * 100 + 11;
|
||||
}
|
||||
|
||||
template <int SerNo>
|
||||
int AddInCppUniquePtr(std::unique_ptr<Abase<SerNo>> obj, int other_val) {
|
||||
return obj->Add(other_val) * 100 + 13;
|
||||
}
|
||||
|
||||
#ifdef PYBIND11_SMART_HOLDER_ENABLED
|
||||
template <int SerNo>
|
||||
void wrap(py::module_ m, const char *py_class_name) {
|
||||
py::classh<Abase<SerNo>, AbaseAlias<SerNo>>(m, py_class_name)
|
||||
.def(py::init<int>(), py::arg("val"))
|
||||
.def("Get", &Abase<SerNo>::Get)
|
||||
.def("Add", &Abase<SerNo>::Add, py::arg("other_val"));
|
||||
|
||||
m.def("AddInCppRawPtr", AddInCppRawPtr<SerNo>, py::arg("obj"), py::arg("other_val"));
|
||||
m.def("AddInCppSharedPtr", AddInCppSharedPtr<SerNo>, py::arg("obj"), py::arg("other_val"));
|
||||
m.def("AddInCppUniquePtr", AddInCppUniquePtr<SerNo>, py::arg("obj"), py::arg("other_val"));
|
||||
}
|
||||
#endif
|
||||
|
||||
} // namespace class_sh_trampoline_basic
|
||||
} // namespace pybind11_tests
|
||||
|
||||
using namespace pybind11_tests::class_sh_trampoline_basic;
|
||||
|
||||
PYBIND11_SMART_HOLDER_TYPE_CASTERS(Abase<0>)
|
||||
PYBIND11_SMART_HOLDER_TYPE_CASTERS(Abase<1>)
|
||||
|
||||
TEST_SUBMODULE(class_sh_trampoline_basic, m) {
|
||||
m.attr("defined_PYBIND11_SMART_HOLDER_ENABLED") =
|
||||
#ifndef PYBIND11_SMART_HOLDER_ENABLED
|
||||
false;
|
||||
#else
|
||||
true;
|
||||
|
||||
wrap<0>(m, "Abase0");
|
||||
wrap<1>(m, "Abase1");
|
||||
#endif // PYBIND11_SMART_HOLDER_ENABLED
|
||||
}
|
62
tests/test_class_sh_trampoline_basic.py
Normal file
62
tests/test_class_sh_trampoline_basic.py
Normal file
@ -0,0 +1,62 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
from pybind11_tests import class_sh_trampoline_basic as m
|
||||
|
||||
if not m.defined_PYBIND11_SMART_HOLDER_ENABLED:
|
||||
pytest.skip("smart_holder not available.", allow_module_level=True)
|
||||
|
||||
|
||||
class PyDrvd0(m.Abase0):
|
||||
def __init__(self, val):
|
||||
super().__init__(val)
|
||||
|
||||
def Add(self, other_val):
|
||||
return self.Get() * 100 + other_val
|
||||
|
||||
|
||||
class PyDrvd1(m.Abase1):
|
||||
def __init__(self, val):
|
||||
super().__init__(val)
|
||||
|
||||
def Add(self, other_val):
|
||||
return self.Get() * 200 + other_val
|
||||
|
||||
|
||||
def test_drvd0_add():
|
||||
drvd = PyDrvd0(74)
|
||||
assert drvd.Add(38) == (74 * 10 + 3) * 100 + 38
|
||||
|
||||
|
||||
def test_drvd0_add_in_cpp_raw_ptr():
|
||||
drvd = PyDrvd0(52)
|
||||
assert m.AddInCppRawPtr(drvd, 27) == ((52 * 10 + 3) * 100 + 27) * 10 + 7
|
||||
|
||||
|
||||
def test_drvd0_add_in_cpp_shared_ptr():
|
||||
while True:
|
||||
drvd = PyDrvd0(36)
|
||||
assert m.AddInCppSharedPtr(drvd, 56) == ((36 * 10 + 3) * 100 + 56) * 100 + 11
|
||||
return # Comment out for manual leak checking (use `top` command).
|
||||
|
||||
|
||||
def test_drvd0_add_in_cpp_unique_ptr():
|
||||
while True:
|
||||
drvd = PyDrvd0(0)
|
||||
with pytest.raises(ValueError) as exc_info:
|
||||
m.AddInCppUniquePtr(drvd, 0)
|
||||
assert (
|
||||
str(exc_info.value)
|
||||
== "Alias class (also known as trampoline) does not inherit from"
|
||||
" py::trampoline_self_life_support, therefore the ownership of this"
|
||||
" instance cannot safely be transferred to C++."
|
||||
)
|
||||
return # Comment out for manual leak checking (use `top` command).
|
||||
|
||||
|
||||
def test_drvd1_add_in_cpp_unique_ptr():
|
||||
while True:
|
||||
drvd = PyDrvd1(25)
|
||||
assert m.AddInCppUniquePtr(drvd, 83) == ((25 * 10 + 3) * 200 + 83) * 100 + 13
|
||||
return # Comment out for manual leak checking (use `top` command).
|
98
tests/test_class_sh_trampoline_self_life_support.cpp
Normal file
98
tests/test_class_sh_trampoline_self_life_support.cpp
Normal file
@ -0,0 +1,98 @@
|
||||
// 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.
|
||||
|
||||
#include "pybind11/smart_holder.h"
|
||||
#include "pybind11/trampoline_self_life_support.h"
|
||||
#include "pybind11_tests.h"
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
namespace pybind11_tests {
|
||||
namespace class_sh_trampoline_self_life_support {
|
||||
|
||||
struct Big5 { // Also known as "rule of five".
|
||||
std::string history;
|
||||
|
||||
explicit Big5(std::string history_start) : history{std::move(history_start)} {}
|
||||
|
||||
Big5(const Big5 &other) { history = other.history + "_CpCtor"; }
|
||||
|
||||
Big5(Big5 &&other) noexcept { history = other.history + "_MvCtor"; }
|
||||
|
||||
Big5 &operator=(const Big5 &other) {
|
||||
history = other.history + "_OpEqLv";
|
||||
return *this;
|
||||
}
|
||||
|
||||
Big5 &operator=(Big5 &&other) noexcept {
|
||||
history = other.history + "_OpEqRv";
|
||||
return *this;
|
||||
}
|
||||
|
||||
virtual ~Big5() = default;
|
||||
|
||||
protected:
|
||||
Big5() : history{"DefaultConstructor"} {}
|
||||
};
|
||||
|
||||
#ifdef PYBIND11_SMART_HOLDER_ENABLED
|
||||
struct Big5Trampoline : Big5, py::trampoline_self_life_support {
|
||||
using Big5::Big5;
|
||||
};
|
||||
#endif
|
||||
|
||||
} // namespace class_sh_trampoline_self_life_support
|
||||
} // namespace pybind11_tests
|
||||
|
||||
using namespace pybind11_tests::class_sh_trampoline_self_life_support;
|
||||
|
||||
PYBIND11_SMART_HOLDER_TYPE_CASTERS(Big5)
|
||||
|
||||
TEST_SUBMODULE(class_sh_trampoline_self_life_support, m) {
|
||||
m.attr("defined_PYBIND11_SMART_HOLDER_ENABLED") =
|
||||
#ifndef PYBIND11_SMART_HOLDER_ENABLED
|
||||
false;
|
||||
#else
|
||||
true;
|
||||
|
||||
py::classh<Big5, Big5Trampoline>(m, "Big5")
|
||||
.def(py::init<std::string>())
|
||||
.def_readonly("history", &Big5::history);
|
||||
|
||||
m.def("action", [](std::unique_ptr<Big5> obj, int action_id) {
|
||||
py::object o2 = py::none();
|
||||
// This is very unusual, but needed to directly exercise the trampoline_self_life_support
|
||||
// CpCtor, MvCtor, operator= lvalue, operator= rvalue.
|
||||
auto *obj_trampoline = dynamic_cast<Big5Trampoline *>(obj.get());
|
||||
if (obj_trampoline != nullptr) {
|
||||
switch (action_id) {
|
||||
case 0: { // CpCtor
|
||||
std::unique_ptr<Big5> cp(new Big5Trampoline(*obj_trampoline));
|
||||
o2 = py::cast(std::move(cp));
|
||||
} break;
|
||||
case 1: { // MvCtor
|
||||
std::unique_ptr<Big5> mv(new Big5Trampoline(std::move(*obj_trampoline)));
|
||||
o2 = py::cast(std::move(mv));
|
||||
} break;
|
||||
case 2: { // operator= lvalue
|
||||
std::unique_ptr<Big5> lv(new Big5Trampoline);
|
||||
*lv = *obj_trampoline; // NOLINT clang-tidy cppcoreguidelines-slicing
|
||||
o2 = py::cast(std::move(lv));
|
||||
} break;
|
||||
case 3: { // operator= rvalue
|
||||
std::unique_ptr<Big5> rv(new Big5Trampoline);
|
||||
*rv = std::move(*obj_trampoline);
|
||||
o2 = py::cast(std::move(rv));
|
||||
} break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
py::object o1 = py::cast(std::move(obj));
|
||||
return py::make_tuple(o1, o2);
|
||||
});
|
||||
#endif // PYBIND11_SMART_HOLDER_ENABLED
|
||||
}
|
41
tests/test_class_sh_trampoline_self_life_support.py
Normal file
41
tests/test_class_sh_trampoline_self_life_support.py
Normal file
@ -0,0 +1,41 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
import pybind11_tests.class_sh_trampoline_self_life_support as m
|
||||
|
||||
if not m.defined_PYBIND11_SMART_HOLDER_ENABLED:
|
||||
pytest.skip("smart_holder not available.", allow_module_level=True)
|
||||
|
||||
|
||||
class PyBig5(m.Big5):
|
||||
pass
|
||||
|
||||
|
||||
def test_m_big5():
|
||||
obj = m.Big5("Seed")
|
||||
assert obj.history == "Seed"
|
||||
o1, o2 = m.action(obj, 0)
|
||||
assert o1 is not obj
|
||||
assert o1.history == "Seed"
|
||||
with pytest.raises(ValueError) as excinfo:
|
||||
_ = obj.history
|
||||
assert "Python instance was disowned" in str(excinfo.value)
|
||||
assert o2 is None
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("action_id", "expected_history"),
|
||||
[
|
||||
(0, "Seed_CpCtor"),
|
||||
(1, "Seed_MvCtor"),
|
||||
(2, "Seed_OpEqLv"),
|
||||
(3, "Seed_OpEqRv"),
|
||||
],
|
||||
)
|
||||
def test_py_big5(action_id, expected_history):
|
||||
obj = PyBig5("Seed")
|
||||
assert obj.history == "Seed"
|
||||
o1, o2 = m.action(obj, action_id)
|
||||
assert o1 is obj
|
||||
assert o2.history == expected_history
|
150
tests/test_class_sh_trampoline_shared_from_this.cpp
Normal file
150
tests/test_class_sh_trampoline_shared_from_this.cpp
Normal file
@ -0,0 +1,150 @@
|
||||
// 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.
|
||||
|
||||
#include "pybind11/smart_holder.h"
|
||||
#include "pybind11_tests.h"
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
namespace pybind11_tests {
|
||||
namespace class_sh_trampoline_shared_from_this {
|
||||
|
||||
struct Sft : std::enable_shared_from_this<Sft> {
|
||||
std::string history;
|
||||
explicit Sft(const std::string &history_seed) : history{history_seed} {}
|
||||
virtual ~Sft() = default;
|
||||
|
||||
#if defined(__clang__)
|
||||
// "Group of 4" begin.
|
||||
// This group is not meant to be used, but will leave a trace in the
|
||||
// history in case something goes wrong.
|
||||
// However, compilers other than clang have a variety of issues. It is not
|
||||
// worth the trouble covering all platforms.
|
||||
Sft(const Sft &other) : enable_shared_from_this(other) { history = other.history + "_CpCtor"; }
|
||||
|
||||
Sft(Sft &&other) noexcept { history = other.history + "_MvCtor"; }
|
||||
|
||||
Sft &operator=(const Sft &other) {
|
||||
history = other.history + "_OpEqLv";
|
||||
return *this;
|
||||
}
|
||||
|
||||
Sft &operator=(Sft &&other) noexcept {
|
||||
history = other.history + "_OpEqRv";
|
||||
return *this;
|
||||
}
|
||||
// "Group of 4" end.
|
||||
#endif
|
||||
};
|
||||
|
||||
struct SftSharedPtrStash {
|
||||
int ser_no;
|
||||
std::vector<std::shared_ptr<Sft>> stash;
|
||||
explicit SftSharedPtrStash(int ser_no) : ser_no{ser_no} {}
|
||||
void Clear() { stash.clear(); }
|
||||
void Add(const std::shared_ptr<Sft> &obj) {
|
||||
if (!obj->history.empty()) {
|
||||
obj->history += "_Stash" + std::to_string(ser_no) + "Add";
|
||||
}
|
||||
stash.push_back(obj);
|
||||
}
|
||||
void AddSharedFromThis(Sft *obj) {
|
||||
auto sft = obj->shared_from_this();
|
||||
if (!sft->history.empty()) {
|
||||
sft->history += "_Stash" + std::to_string(ser_no) + "AddSharedFromThis";
|
||||
}
|
||||
stash.push_back(sft);
|
||||
}
|
||||
std::string history(unsigned i) {
|
||||
if (i < stash.size()) {
|
||||
return stash[i]->history;
|
||||
}
|
||||
return "OutOfRange";
|
||||
}
|
||||
long use_count(unsigned i) {
|
||||
if (i < stash.size()) {
|
||||
return stash[i].use_count();
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
};
|
||||
|
||||
#ifdef PYBIND11_SMART_HOLDER_ENABLED
|
||||
struct SftTrampoline : Sft, py::trampoline_self_life_support {
|
||||
using Sft::Sft;
|
||||
};
|
||||
#endif
|
||||
|
||||
long use_count(const std::shared_ptr<Sft> &obj) { return obj.use_count(); }
|
||||
|
||||
long pass_shared_ptr(const std::shared_ptr<Sft> &obj) {
|
||||
auto sft = obj->shared_from_this();
|
||||
if (!sft->history.empty()) {
|
||||
sft->history += "_PassSharedPtr";
|
||||
}
|
||||
return sft.use_count();
|
||||
}
|
||||
|
||||
std::string pass_unique_ptr_cref(const std::unique_ptr<Sft> &obj) {
|
||||
return obj ? obj->history : "<NULLPTR>";
|
||||
}
|
||||
void pass_unique_ptr_rref(std::unique_ptr<Sft> &&) {
|
||||
throw std::runtime_error("Expected to not be reached.");
|
||||
}
|
||||
|
||||
Sft *make_pure_cpp_sft_raw_ptr(const std::string &history_seed) { return new Sft{history_seed}; }
|
||||
|
||||
std::unique_ptr<Sft> make_pure_cpp_sft_unq_ptr(const std::string &history_seed) {
|
||||
return std::unique_ptr<Sft>(new Sft{history_seed});
|
||||
}
|
||||
|
||||
std::shared_ptr<Sft> make_pure_cpp_sft_shd_ptr(const std::string &history_seed) {
|
||||
return std::make_shared<Sft>(history_seed);
|
||||
}
|
||||
|
||||
std::shared_ptr<Sft> pass_through_shd_ptr(const std::shared_ptr<Sft> &obj) { return obj; }
|
||||
|
||||
} // namespace class_sh_trampoline_shared_from_this
|
||||
} // namespace pybind11_tests
|
||||
|
||||
using namespace pybind11_tests::class_sh_trampoline_shared_from_this;
|
||||
|
||||
PYBIND11_SMART_HOLDER_TYPE_CASTERS(Sft)
|
||||
PYBIND11_SMART_HOLDER_TYPE_CASTERS(SftSharedPtrStash)
|
||||
|
||||
TEST_SUBMODULE(class_sh_trampoline_shared_from_this, m) {
|
||||
m.attr("defined_PYBIND11_SMART_HOLDER_ENABLED") =
|
||||
#ifndef PYBIND11_SMART_HOLDER_ENABLED
|
||||
false;
|
||||
#else
|
||||
true;
|
||||
|
||||
py::classh<Sft, SftTrampoline>(m, "Sft")
|
||||
.def(py::init<const std::string &>())
|
||||
.def(py::init([](const std::string &history, int) {
|
||||
return std::make_shared<SftTrampoline>(history);
|
||||
}))
|
||||
.def_readonly("history", &Sft::history)
|
||||
// This leads to multiple entries in registered_instances:
|
||||
.def(py::init([](const std::shared_ptr<Sft> &existing) { return existing; }));
|
||||
|
||||
py::classh<SftSharedPtrStash>(m, "SftSharedPtrStash")
|
||||
.def(py::init<int>())
|
||||
.def("Clear", &SftSharedPtrStash::Clear)
|
||||
.def("Add", &SftSharedPtrStash::Add)
|
||||
.def("AddSharedFromThis", &SftSharedPtrStash::AddSharedFromThis)
|
||||
.def("history", &SftSharedPtrStash::history)
|
||||
.def("use_count", &SftSharedPtrStash::use_count);
|
||||
|
||||
m.def("use_count", use_count);
|
||||
m.def("pass_shared_ptr", pass_shared_ptr);
|
||||
m.def("pass_unique_ptr_cref", pass_unique_ptr_cref);
|
||||
m.def("pass_unique_ptr_rref", pass_unique_ptr_rref);
|
||||
m.def("make_pure_cpp_sft_raw_ptr", make_pure_cpp_sft_raw_ptr);
|
||||
m.def("make_pure_cpp_sft_unq_ptr", make_pure_cpp_sft_unq_ptr);
|
||||
m.def("make_pure_cpp_sft_shd_ptr", make_pure_cpp_sft_shd_ptr);
|
||||
m.def("pass_through_shd_ptr", pass_through_shd_ptr);
|
||||
#endif // PYBIND11_SMART_HOLDER_ENABLED
|
||||
}
|
250
tests/test_class_sh_trampoline_shared_from_this.py
Normal file
250
tests/test_class_sh_trampoline_shared_from_this.py
Normal file
@ -0,0 +1,250 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
import weakref
|
||||
|
||||
import pytest
|
||||
|
||||
import env
|
||||
import pybind11_tests.class_sh_trampoline_shared_from_this as m
|
||||
|
||||
if not m.defined_PYBIND11_SMART_HOLDER_ENABLED:
|
||||
pytest.skip("smart_holder not available.", allow_module_level=True)
|
||||
|
||||
|
||||
class PySft(m.Sft):
|
||||
pass
|
||||
|
||||
|
||||
def test_release_and_shared_from_this():
|
||||
# Exercises the most direct path from building a shared_from_this-visible
|
||||
# shared_ptr to calling shared_from_this.
|
||||
obj = PySft("PySft")
|
||||
assert obj.history == "PySft"
|
||||
assert m.use_count(obj) == 1
|
||||
assert m.pass_shared_ptr(obj) == 2
|
||||
assert obj.history == "PySft_PassSharedPtr"
|
||||
assert m.use_count(obj) == 1
|
||||
assert m.pass_shared_ptr(obj) == 2
|
||||
assert obj.history == "PySft_PassSharedPtr_PassSharedPtr"
|
||||
assert m.use_count(obj) == 1
|
||||
|
||||
|
||||
def test_release_and_shared_from_this_leak():
|
||||
obj = PySft("")
|
||||
while True:
|
||||
m.pass_shared_ptr(obj)
|
||||
assert not obj.history
|
||||
assert m.use_count(obj) == 1
|
||||
break # Comment out for manual leak checking (use `top` command).
|
||||
|
||||
|
||||
def test_release_and_stash():
|
||||
# Exercises correct functioning of guarded_delete weak_ptr.
|
||||
obj = PySft("PySft")
|
||||
stash1 = m.SftSharedPtrStash(1)
|
||||
stash1.Add(obj)
|
||||
exp_hist = "PySft_Stash1Add"
|
||||
assert obj.history == exp_hist
|
||||
assert m.use_count(obj) == 2
|
||||
assert stash1.history(0) == exp_hist
|
||||
assert stash1.use_count(0) == 1
|
||||
assert m.pass_shared_ptr(obj) == 3
|
||||
exp_hist += "_PassSharedPtr"
|
||||
assert obj.history == exp_hist
|
||||
assert m.use_count(obj) == 2
|
||||
assert stash1.history(0) == exp_hist
|
||||
assert stash1.use_count(0) == 1
|
||||
stash2 = m.SftSharedPtrStash(2)
|
||||
stash2.Add(obj)
|
||||
exp_hist += "_Stash2Add"
|
||||
assert obj.history == exp_hist
|
||||
assert m.use_count(obj) == 3
|
||||
assert stash2.history(0) == exp_hist
|
||||
assert stash2.use_count(0) == 2
|
||||
stash2.Add(obj)
|
||||
exp_hist += "_Stash2Add"
|
||||
assert obj.history == exp_hist
|
||||
assert m.use_count(obj) == 4
|
||||
assert stash1.history(0) == exp_hist
|
||||
assert stash1.use_count(0) == 3
|
||||
assert stash2.history(0) == exp_hist
|
||||
assert stash2.use_count(0) == 3
|
||||
assert stash2.history(1) == exp_hist
|
||||
assert stash2.use_count(1) == 3
|
||||
del obj
|
||||
assert stash2.history(0) == exp_hist
|
||||
assert stash2.use_count(0) == 3
|
||||
assert stash2.history(1) == exp_hist
|
||||
assert stash2.use_count(1) == 3
|
||||
stash2.Clear()
|
||||
assert stash1.history(0) == exp_hist
|
||||
assert stash1.use_count(0) == 1
|
||||
|
||||
|
||||
def test_release_and_stash_leak():
|
||||
obj = PySft("")
|
||||
while True:
|
||||
stash1 = m.SftSharedPtrStash(1)
|
||||
stash1.Add(obj)
|
||||
assert not obj.history
|
||||
assert m.use_count(obj) == 2
|
||||
assert stash1.use_count(0) == 1
|
||||
stash1.Add(obj)
|
||||
assert not obj.history
|
||||
assert m.use_count(obj) == 3
|
||||
assert stash1.use_count(0) == 2
|
||||
assert stash1.use_count(1) == 2
|
||||
break # Comment out for manual leak checking (use `top` command).
|
||||
|
||||
|
||||
def test_release_and_stash_via_shared_from_this():
|
||||
# Exercises that the smart_holder vptr is invisible to the shared_from_this mechanism.
|
||||
obj = PySft("PySft")
|
||||
stash1 = m.SftSharedPtrStash(1)
|
||||
with pytest.raises(RuntimeError) as exc_info:
|
||||
stash1.AddSharedFromThis(obj)
|
||||
assert str(exc_info.value) == "bad_weak_ptr"
|
||||
stash1.Add(obj)
|
||||
assert obj.history == "PySft_Stash1Add"
|
||||
assert stash1.use_count(0) == 1
|
||||
stash1.AddSharedFromThis(obj)
|
||||
assert obj.history == "PySft_Stash1Add_Stash1AddSharedFromThis"
|
||||
assert stash1.use_count(0) == 2
|
||||
assert stash1.use_count(1) == 2
|
||||
|
||||
|
||||
def test_release_and_stash_via_shared_from_this_leak():
|
||||
obj = PySft("")
|
||||
while True:
|
||||
stash1 = m.SftSharedPtrStash(1)
|
||||
with pytest.raises(RuntimeError) as exc_info:
|
||||
stash1.AddSharedFromThis(obj)
|
||||
assert str(exc_info.value) == "bad_weak_ptr"
|
||||
stash1.Add(obj)
|
||||
assert not obj.history
|
||||
assert stash1.use_count(0) == 1
|
||||
stash1.AddSharedFromThis(obj)
|
||||
assert not obj.history
|
||||
assert stash1.use_count(0) == 2
|
||||
assert stash1.use_count(1) == 2
|
||||
break # Comment out for manual leak checking (use `top` command).
|
||||
|
||||
|
||||
def test_pass_released_shared_ptr_as_unique_ptr():
|
||||
# Exercises that returning a unique_ptr fails while a shared_from_this
|
||||
# visible shared_ptr exists.
|
||||
obj = PySft("PySft")
|
||||
stash1 = m.SftSharedPtrStash(1)
|
||||
stash1.Add(obj) # Releases shared_ptr to C++.
|
||||
assert m.pass_unique_ptr_cref(obj) == "PySft_Stash1Add"
|
||||
assert obj.history == "PySft_Stash1Add"
|
||||
with pytest.raises(ValueError) as exc_info:
|
||||
m.pass_unique_ptr_rref(obj)
|
||||
assert str(exc_info.value) == (
|
||||
"Python instance is currently owned by a std::shared_ptr."
|
||||
)
|
||||
assert obj.history == "PySft_Stash1Add"
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"make_f",
|
||||
[
|
||||
m.make_pure_cpp_sft_raw_ptr,
|
||||
m.make_pure_cpp_sft_unq_ptr,
|
||||
m.make_pure_cpp_sft_shd_ptr,
|
||||
],
|
||||
)
|
||||
def test_pure_cpp_sft_raw_ptr(make_f):
|
||||
# Exercises void_cast_raw_ptr logic for different situations.
|
||||
obj = make_f("PureCppSft")
|
||||
assert m.pass_shared_ptr(obj) == 3
|
||||
assert obj.history == "PureCppSft_PassSharedPtr"
|
||||
obj = make_f("PureCppSft")
|
||||
stash1 = m.SftSharedPtrStash(1)
|
||||
stash1.AddSharedFromThis(obj)
|
||||
assert obj.history == "PureCppSft_Stash1AddSharedFromThis"
|
||||
|
||||
|
||||
def test_multiple_registered_instances_for_same_pointee():
|
||||
obj0 = PySft("PySft")
|
||||
obj0.attachment_in_dict = "Obj0"
|
||||
assert m.pass_through_shd_ptr(obj0) is obj0
|
||||
while True:
|
||||
obj = m.Sft(obj0)
|
||||
assert obj is not obj0
|
||||
obj_pt = m.pass_through_shd_ptr(obj)
|
||||
# Unpredictable! Because registered_instances is as std::unordered_multimap.
|
||||
assert obj_pt is obj0 or obj_pt is obj
|
||||
# Multiple registered_instances for the same pointee can lead to unpredictable results:
|
||||
if obj_pt is obj0:
|
||||
assert obj_pt.attachment_in_dict == "Obj0"
|
||||
else:
|
||||
assert not hasattr(obj_pt, "attachment_in_dict")
|
||||
assert obj0.history == "PySft"
|
||||
break # Comment out for manual leak checking (use `top` command).
|
||||
|
||||
|
||||
def test_multiple_registered_instances_for_same_pointee_leak():
|
||||
obj0 = PySft("")
|
||||
while True:
|
||||
stash1 = m.SftSharedPtrStash(1)
|
||||
stash1.Add(m.Sft(obj0))
|
||||
assert stash1.use_count(0) == 1
|
||||
stash1.Add(m.Sft(obj0))
|
||||
assert stash1.use_count(0) == 1
|
||||
assert stash1.use_count(1) == 1
|
||||
assert not obj0.history
|
||||
break # Comment out for manual leak checking (use `top` command).
|
||||
|
||||
|
||||
def test_multiple_registered_instances_for_same_pointee_recursive():
|
||||
while True:
|
||||
obj0 = PySft("PySft")
|
||||
if not env.PYPY:
|
||||
obj0_wr = weakref.ref(obj0)
|
||||
obj = obj0
|
||||
# This loop creates a chain of instances linked by shared_ptrs.
|
||||
for _ in range(10):
|
||||
obj_next = m.Sft(obj)
|
||||
assert obj_next is not obj
|
||||
obj = obj_next
|
||||
del obj_next
|
||||
assert obj.history == "PySft"
|
||||
del obj0
|
||||
if not env.PYPY:
|
||||
assert obj0_wr() is not None
|
||||
del obj # This releases the chain recursively.
|
||||
if not env.PYPY:
|
||||
assert obj0_wr() is None
|
||||
break # Comment out for manual leak checking (use `top` command).
|
||||
|
||||
|
||||
# As of 2021-07-10 the pybind11 GitHub Actions valgrind build uses Python 3.9.
|
||||
WORKAROUND_ENABLING_ROLLBACK_OF_PR3068 = env.LINUX and sys.version_info == (3, 9)
|
||||
|
||||
|
||||
def test_std_make_shared_factory():
|
||||
class PySftMakeShared(m.Sft):
|
||||
def __init__(self, history):
|
||||
super().__init__(history, 0)
|
||||
|
||||
obj = PySftMakeShared("PySftMakeShared")
|
||||
assert obj.history == "PySftMakeShared"
|
||||
if WORKAROUND_ENABLING_ROLLBACK_OF_PR3068:
|
||||
try:
|
||||
m.pass_through_shd_ptr(obj)
|
||||
except RuntimeError as e:
|
||||
str_exc_info_value = str(e)
|
||||
else:
|
||||
str_exc_info_value = "RuntimeError NOT RAISED"
|
||||
else:
|
||||
with pytest.raises(RuntimeError) as exc_info:
|
||||
m.pass_through_shd_ptr(obj)
|
||||
str_exc_info_value = str(exc_info.value)
|
||||
assert (
|
||||
str_exc_info_value
|
||||
== "smart_holder_type_casters load_as_shared_ptr failure: not implemented:"
|
||||
" trampoline-self-life-support for external shared_ptr to type inheriting"
|
||||
" from std::enable_shared_from_this."
|
||||
)
|
105
tests/test_class_sh_trampoline_shared_ptr_cpp_arg.cpp
Normal file
105
tests/test_class_sh_trampoline_shared_ptr_cpp_arg.cpp
Normal file
@ -0,0 +1,105 @@
|
||||
// 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.
|
||||
|
||||
#include "pybind11/smart_holder.h"
|
||||
#include "pybind11_tests.h"
|
||||
|
||||
#include <utility>
|
||||
|
||||
namespace pybind11_tests {
|
||||
namespace class_sh_trampoline_shared_ptr_cpp_arg {
|
||||
|
||||
// For testing whether a python subclass of a C++ object dies when the
|
||||
// last python reference is lost
|
||||
struct SpBase {
|
||||
// returns true if the base virtual function is called
|
||||
virtual bool is_base_used() { return true; }
|
||||
|
||||
// returns true if there's an associated python instance
|
||||
bool has_python_instance() {
|
||||
auto *tinfo = py::detail::get_type_info(typeid(SpBase));
|
||||
return (bool) py::detail::get_object_handle(this, tinfo);
|
||||
}
|
||||
|
||||
SpBase() = default;
|
||||
SpBase(const SpBase &) = delete;
|
||||
virtual ~SpBase() = default;
|
||||
};
|
||||
|
||||
std::shared_ptr<SpBase> pass_through_shd_ptr(const std::shared_ptr<SpBase> &obj) { return obj; }
|
||||
|
||||
struct PySpBase : SpBase {
|
||||
using SpBase::SpBase;
|
||||
bool is_base_used() override { PYBIND11_OVERRIDE(bool, SpBase, is_base_used); }
|
||||
};
|
||||
|
||||
struct SpBaseTester {
|
||||
std::shared_ptr<SpBase> get_object() const { return m_obj; }
|
||||
void set_object(std::shared_ptr<SpBase> obj) { m_obj = std::move(obj); }
|
||||
bool is_base_used() { return m_obj->is_base_used(); }
|
||||
bool has_instance() { return (bool) m_obj; }
|
||||
bool has_python_instance() { return m_obj && m_obj->has_python_instance(); }
|
||||
void set_nonpython_instance() { m_obj = std::make_shared<SpBase>(); }
|
||||
std::shared_ptr<SpBase> m_obj;
|
||||
};
|
||||
|
||||
// For testing that a C++ class without an alias does not retain the python
|
||||
// portion of the object
|
||||
struct SpGoAway {};
|
||||
|
||||
struct SpGoAwayTester {
|
||||
std::shared_ptr<SpGoAway> m_obj;
|
||||
};
|
||||
|
||||
} // namespace class_sh_trampoline_shared_ptr_cpp_arg
|
||||
} // namespace pybind11_tests
|
||||
|
||||
using namespace pybind11_tests::class_sh_trampoline_shared_ptr_cpp_arg;
|
||||
|
||||
PYBIND11_SMART_HOLDER_TYPE_CASTERS(SpBase)
|
||||
PYBIND11_SMART_HOLDER_TYPE_CASTERS(SpBaseTester)
|
||||
PYBIND11_SMART_HOLDER_TYPE_CASTERS(SpGoAway)
|
||||
PYBIND11_SMART_HOLDER_TYPE_CASTERS(SpGoAwayTester)
|
||||
|
||||
TEST_SUBMODULE(class_sh_trampoline_shared_ptr_cpp_arg, m) {
|
||||
m.attr("defined_PYBIND11_SMART_HOLDER_ENABLED") =
|
||||
#ifndef PYBIND11_SMART_HOLDER_ENABLED
|
||||
false;
|
||||
#else
|
||||
true;
|
||||
|
||||
// For testing whether a python subclass of a C++ object dies when the
|
||||
// last python reference is lost
|
||||
|
||||
py::classh<SpBase, PySpBase>(m, "SpBase")
|
||||
.def(py::init<>())
|
||||
.def(py::init([](int) { return std::make_shared<PySpBase>(); }))
|
||||
.def("is_base_used", &SpBase::is_base_used)
|
||||
.def("has_python_instance", &SpBase::has_python_instance);
|
||||
|
||||
m.def("pass_through_shd_ptr", pass_through_shd_ptr);
|
||||
m.def("pass_through_shd_ptr_release_gil",
|
||||
pass_through_shd_ptr,
|
||||
py::call_guard<py::gil_scoped_release>()); // PR #4196
|
||||
|
||||
py::classh<SpBaseTester>(m, "SpBaseTester")
|
||||
.def(py::init<>())
|
||||
.def("get_object", &SpBaseTester::get_object)
|
||||
.def("set_object", &SpBaseTester::set_object)
|
||||
.def("is_base_used", &SpBaseTester::is_base_used)
|
||||
.def("has_instance", &SpBaseTester::has_instance)
|
||||
.def("has_python_instance", &SpBaseTester::has_python_instance)
|
||||
.def("set_nonpython_instance", &SpBaseTester::set_nonpython_instance)
|
||||
.def_readwrite("obj", &SpBaseTester::m_obj);
|
||||
|
||||
// For testing that a C++ class without an alias does not retain the python
|
||||
// portion of the object
|
||||
|
||||
py::classh<SpGoAway>(m, "SpGoAway").def(py::init<>());
|
||||
|
||||
py::classh<SpGoAwayTester>(m, "SpGoAwayTester")
|
||||
.def(py::init<>())
|
||||
.def_readwrite("obj", &SpGoAwayTester::m_obj);
|
||||
#endif // PYBIND11_SMART_HOLDER_ENABLED
|
||||
}
|
151
tests/test_class_sh_trampoline_shared_ptr_cpp_arg.py
Normal file
151
tests/test_class_sh_trampoline_shared_ptr_cpp_arg.py
Normal file
@ -0,0 +1,151 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
import pybind11_tests.class_sh_trampoline_shared_ptr_cpp_arg as m
|
||||
|
||||
if not m.defined_PYBIND11_SMART_HOLDER_ENABLED:
|
||||
pytest.skip("smart_holder not available.", allow_module_level=True)
|
||||
|
||||
|
||||
def test_shared_ptr_cpp_arg():
|
||||
import weakref
|
||||
|
||||
class PyChild(m.SpBase):
|
||||
def is_base_used(self):
|
||||
return False
|
||||
|
||||
tester = m.SpBaseTester()
|
||||
|
||||
obj = PyChild()
|
||||
objref = weakref.ref(obj)
|
||||
|
||||
# Pass the last python reference to the C++ function
|
||||
tester.set_object(obj)
|
||||
del obj
|
||||
pytest.gc_collect()
|
||||
|
||||
# python reference is still around since C++ has it now
|
||||
assert objref() is not None
|
||||
assert tester.is_base_used() is False
|
||||
assert tester.obj.is_base_used() is False
|
||||
assert tester.get_object() is objref()
|
||||
|
||||
|
||||
def test_shared_ptr_cpp_prop():
|
||||
class PyChild(m.SpBase):
|
||||
def is_base_used(self):
|
||||
return False
|
||||
|
||||
tester = m.SpBaseTester()
|
||||
|
||||
# Set the last python reference as a property of the C++ object
|
||||
tester.obj = PyChild()
|
||||
pytest.gc_collect()
|
||||
|
||||
# python reference is still around since C++ has it now
|
||||
assert tester.is_base_used() is False
|
||||
assert tester.has_python_instance() is True
|
||||
assert tester.obj.is_base_used() is False
|
||||
assert tester.obj.has_python_instance() is True
|
||||
|
||||
|
||||
def test_shared_ptr_arg_identity():
|
||||
import weakref
|
||||
|
||||
tester = m.SpBaseTester()
|
||||
|
||||
obj = m.SpBase()
|
||||
objref = weakref.ref(obj)
|
||||
|
||||
tester.set_object(obj)
|
||||
del obj
|
||||
pytest.gc_collect()
|
||||
|
||||
# NOTE: the behavior below is DIFFERENT from PR #2839
|
||||
# python reference is gone because it is not an Alias instance
|
||||
assert objref() is None
|
||||
assert tester.has_python_instance() is False
|
||||
|
||||
|
||||
def test_shared_ptr_alias_nonpython():
|
||||
tester = m.SpBaseTester()
|
||||
|
||||
# C++ creates the object, a python instance shouldn't exist
|
||||
tester.set_nonpython_instance()
|
||||
assert tester.is_base_used() is True
|
||||
assert tester.has_instance() is True
|
||||
assert tester.has_python_instance() is False
|
||||
|
||||
# Now a python instance exists
|
||||
cobj = tester.get_object()
|
||||
assert cobj.has_python_instance()
|
||||
assert tester.has_instance() is True
|
||||
assert tester.has_python_instance() is True
|
||||
|
||||
# Now it's gone
|
||||
del cobj
|
||||
pytest.gc_collect()
|
||||
assert tester.has_instance() is True
|
||||
assert tester.has_python_instance() is False
|
||||
|
||||
# When we pass it as an arg to a new tester the python instance should
|
||||
# disappear because it wasn't created with an alias
|
||||
new_tester = m.SpBaseTester()
|
||||
|
||||
cobj = tester.get_object()
|
||||
assert cobj.has_python_instance()
|
||||
|
||||
new_tester.set_object(cobj)
|
||||
assert tester.has_python_instance() is True
|
||||
assert new_tester.has_python_instance() is True
|
||||
|
||||
del cobj
|
||||
pytest.gc_collect()
|
||||
|
||||
# Gone!
|
||||
assert tester.has_instance() is True
|
||||
assert tester.has_python_instance() is False
|
||||
assert new_tester.has_instance() is True
|
||||
assert new_tester.has_python_instance() is False
|
||||
|
||||
|
||||
def test_shared_ptr_goaway():
|
||||
import weakref
|
||||
|
||||
tester = m.SpGoAwayTester()
|
||||
|
||||
obj = m.SpGoAway()
|
||||
objref = weakref.ref(obj)
|
||||
|
||||
assert tester.obj is None
|
||||
|
||||
tester.obj = obj
|
||||
del obj
|
||||
pytest.gc_collect()
|
||||
|
||||
# python reference is no longer around
|
||||
assert objref() is None
|
||||
# C++ reference is still around
|
||||
assert tester.obj is not None
|
||||
|
||||
|
||||
def test_infinite():
|
||||
tester = m.SpBaseTester()
|
||||
while True:
|
||||
tester.set_object(m.SpBase())
|
||||
break # Comment out for manual leak checking (use `top` command).
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"pass_through_func", [m.pass_through_shd_ptr, m.pass_through_shd_ptr_release_gil]
|
||||
)
|
||||
def test_std_make_shared_factory(pass_through_func):
|
||||
class PyChild(m.SpBase):
|
||||
def __init__(self):
|
||||
super().__init__(0)
|
||||
|
||||
obj = PyChild()
|
||||
while True:
|
||||
assert pass_through_func(obj) is obj
|
||||
break # Comment out for manual leak checking (use `top` command).
|
75
tests/test_class_sh_trampoline_unique_ptr.cpp
Normal file
75
tests/test_class_sh_trampoline_unique_ptr.cpp
Normal file
@ -0,0 +1,75 @@
|
||||
// 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.
|
||||
|
||||
#include "pybind11/smart_holder.h"
|
||||
#include "pybind11/trampoline_self_life_support.h"
|
||||
#include "pybind11_tests.h"
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace pybind11_tests {
|
||||
namespace class_sh_trampoline_unique_ptr {
|
||||
|
||||
class Class {
|
||||
public:
|
||||
virtual ~Class() = default;
|
||||
|
||||
void setVal(std::uint64_t val) { val_ = val; }
|
||||
std::uint64_t getVal() const { return val_; }
|
||||
|
||||
virtual std::unique_ptr<Class> clone() const = 0;
|
||||
virtual int foo() const = 0;
|
||||
|
||||
protected:
|
||||
Class() = default;
|
||||
|
||||
// Some compilers complain about implicitly defined versions of some of the following:
|
||||
Class(const Class &) = default;
|
||||
|
||||
private:
|
||||
std::uint64_t val_ = 0;
|
||||
};
|
||||
|
||||
} // namespace class_sh_trampoline_unique_ptr
|
||||
} // namespace pybind11_tests
|
||||
|
||||
PYBIND11_SMART_HOLDER_TYPE_CASTERS(pybind11_tests::class_sh_trampoline_unique_ptr::Class)
|
||||
|
||||
namespace pybind11_tests {
|
||||
namespace class_sh_trampoline_unique_ptr {
|
||||
|
||||
#ifdef PYBIND11_SMART_HOLDER_ENABLED
|
||||
class PyClass : public Class, public py::trampoline_self_life_support {
|
||||
public:
|
||||
std::unique_ptr<Class> clone() const override {
|
||||
PYBIND11_OVERRIDE_PURE(std::unique_ptr<Class>, Class, clone);
|
||||
}
|
||||
|
||||
int foo() const override { PYBIND11_OVERRIDE_PURE(int, Class, foo); }
|
||||
};
|
||||
#endif
|
||||
|
||||
} // namespace class_sh_trampoline_unique_ptr
|
||||
} // namespace pybind11_tests
|
||||
|
||||
TEST_SUBMODULE(class_sh_trampoline_unique_ptr, m) {
|
||||
m.attr("defined_PYBIND11_SMART_HOLDER_ENABLED") =
|
||||
#ifndef PYBIND11_SMART_HOLDER_ENABLED
|
||||
false;
|
||||
#else
|
||||
true;
|
||||
|
||||
using namespace pybind11_tests::class_sh_trampoline_unique_ptr;
|
||||
|
||||
py::classh<Class, PyClass>(m, "Class")
|
||||
.def(py::init<>())
|
||||
.def("set_val", &Class::setVal)
|
||||
.def("get_val", &Class::getVal)
|
||||
.def("clone", &Class::clone)
|
||||
.def("foo", &Class::foo);
|
||||
|
||||
m.def("clone", [](const Class &obj) { return obj.clone(); });
|
||||
m.def("clone_and_foo", [](const Class &obj) { return obj.clone()->foo(); });
|
||||
#endif // PYBIND11_SMART_HOLDER_ENABLED
|
||||
}
|
36
tests/test_class_sh_trampoline_unique_ptr.py
Normal file
36
tests/test_class_sh_trampoline_unique_ptr.py
Normal file
@ -0,0 +1,36 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
import pybind11_tests.class_sh_trampoline_unique_ptr as m
|
||||
|
||||
if not m.defined_PYBIND11_SMART_HOLDER_ENABLED:
|
||||
pytest.skip("smart_holder not available.", allow_module_level=True)
|
||||
|
||||
|
||||
class MyClass(m.Class):
|
||||
def foo(self):
|
||||
return 10 + self.get_val()
|
||||
|
||||
def clone(self):
|
||||
cloned = MyClass()
|
||||
cloned.set_val(self.get_val() + 3)
|
||||
return cloned
|
||||
|
||||
|
||||
def test_m_clone():
|
||||
obj = MyClass()
|
||||
while True:
|
||||
obj.set_val(5)
|
||||
obj = m.clone(obj)
|
||||
assert obj.get_val() == 5 + 3
|
||||
assert obj.foo() == 10 + 5 + 3
|
||||
return # Comment out for manual leak checking (use `top` command).
|
||||
|
||||
|
||||
def test_m_clone_and_foo():
|
||||
obj = MyClass()
|
||||
obj.set_val(7)
|
||||
while True:
|
||||
assert m.clone_and_foo(obj) == 10 + 7 + 3
|
||||
return # Comment out for manual leak checking (use `top` command).
|
47
tests/test_class_sh_unique_ptr_custom_deleter.cpp
Normal file
47
tests/test_class_sh_unique_ptr_custom_deleter.cpp
Normal file
@ -0,0 +1,47 @@
|
||||
#include <pybind11/smart_holder.h>
|
||||
|
||||
#include "pybind11_tests.h"
|
||||
|
||||
#include <memory>
|
||||
|
||||
namespace pybind11_tests {
|
||||
namespace class_sh_unique_ptr_custom_deleter {
|
||||
|
||||
// Reduced from a PyCLIF use case in the wild by @wangxf123456.
|
||||
class Pet {
|
||||
public:
|
||||
using Ptr = std::unique_ptr<Pet, std::function<void(Pet *)>>;
|
||||
|
||||
std::string name;
|
||||
|
||||
static Ptr New(const std::string &name) {
|
||||
return Ptr(new Pet(name), std::default_delete<Pet>());
|
||||
}
|
||||
|
||||
private:
|
||||
explicit Pet(const std::string &name) : name(name) {}
|
||||
};
|
||||
|
||||
} // namespace class_sh_unique_ptr_custom_deleter
|
||||
} // namespace pybind11_tests
|
||||
|
||||
PYBIND11_SMART_HOLDER_TYPE_CASTERS(pybind11_tests::class_sh_unique_ptr_custom_deleter::Pet)
|
||||
|
||||
namespace pybind11_tests {
|
||||
namespace class_sh_unique_ptr_custom_deleter {
|
||||
|
||||
TEST_SUBMODULE(class_sh_unique_ptr_custom_deleter, m) {
|
||||
m.attr("defined_PYBIND11_SMART_HOLDER_ENABLED") =
|
||||
#ifndef PYBIND11_SMART_HOLDER_ENABLED
|
||||
false;
|
||||
#else
|
||||
true;
|
||||
|
||||
py::classh<Pet>(m, "Pet").def_readwrite("name", &Pet::name);
|
||||
|
||||
m.def("create", &Pet::New);
|
||||
#endif // PYBIND11_SMART_HOLDER_ENABLED
|
||||
}
|
||||
|
||||
} // namespace class_sh_unique_ptr_custom_deleter
|
||||
} // namespace pybind11_tests
|
13
tests/test_class_sh_unique_ptr_custom_deleter.py
Normal file
13
tests/test_class_sh_unique_ptr_custom_deleter.py
Normal file
@ -0,0 +1,13 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
from pybind11_tests import class_sh_unique_ptr_custom_deleter as m
|
||||
|
||||
if not m.defined_PYBIND11_SMART_HOLDER_ENABLED:
|
||||
pytest.skip("smart_holder not available.", allow_module_level=True)
|
||||
|
||||
|
||||
def test_create():
|
||||
pet = m.create("abc")
|
||||
assert pet.name == "abc"
|
67
tests/test_class_sh_unique_ptr_member.cpp
Normal file
67
tests/test_class_sh_unique_ptr_member.cpp
Normal file
@ -0,0 +1,67 @@
|
||||
#include <pybind11/smart_holder.h>
|
||||
|
||||
#include "pybind11_tests.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; }
|
||||
|
||||
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) {
|
||||
m.attr("defined_PYBIND11_SMART_HOLDER_ENABLED") =
|
||||
#ifndef PYBIND11_SMART_HOLDER_ENABLED
|
||||
false;
|
||||
#else
|
||||
true;
|
||||
|
||||
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);
|
||||
#endif // PYBIND11_SMART_HOLDER_ENABLED
|
||||
}
|
||||
|
||||
} // namespace class_sh_unique_ptr_member
|
||||
} // namespace pybind11_tests
|
29
tests/test_class_sh_unique_ptr_member.py
Normal file
29
tests/test_class_sh_unique_ptr_member.py
Normal file
@ -0,0 +1,29 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
from pybind11_tests import class_sh_unique_ptr_member as m
|
||||
|
||||
if not m.defined_PYBIND11_SMART_HOLDER_ENABLED:
|
||||
pytest.skip("smart_holder not available.", allow_module_level=True)
|
||||
|
||||
|
||||
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(ValueError, match="Python instance was disowned"):
|
||||
obj.get_int()
|
||||
assert owner.is_owner()
|
||||
reclaimed = getattr(owner, give_up_ownership_via)()
|
||||
assert not owner.is_owner()
|
||||
assert reclaimed.get_int() == 213
|
75
tests/test_class_sh_virtual_py_cpp_mix.cpp
Normal file
75
tests/test_class_sh_virtual_py_cpp_mix.cpp
Normal file
@ -0,0 +1,75 @@
|
||||
#include <pybind11/smart_holder.h>
|
||||
|
||||
#include "pybind11_tests.h"
|
||||
|
||||
#include <memory>
|
||||
|
||||
namespace pybind11_tests {
|
||||
namespace class_sh_virtual_py_cpp_mix {
|
||||
|
||||
class Base {
|
||||
public:
|
||||
virtual ~Base() = default;
|
||||
virtual int get() const { return 101; }
|
||||
|
||||
// Some compilers complain about implicitly defined versions of some of the following:
|
||||
Base() = default;
|
||||
Base(const Base &) = default;
|
||||
};
|
||||
|
||||
class CppDerivedPlain : public Base {
|
||||
public:
|
||||
int get() const override { return 202; }
|
||||
};
|
||||
|
||||
class CppDerived : public Base {
|
||||
public:
|
||||
int get() const override { return 212; }
|
||||
};
|
||||
|
||||
int get_from_cpp_plainc_ptr(const Base *b) { return b->get() + 4000; }
|
||||
|
||||
int get_from_cpp_unique_ptr(std::unique_ptr<Base> b) { return b->get() + 5000; }
|
||||
|
||||
#ifdef PYBIND11_SMART_HOLDER_ENABLED
|
||||
|
||||
struct BaseVirtualOverrider : Base, py::trampoline_self_life_support {
|
||||
using Base::Base;
|
||||
|
||||
int get() const override { PYBIND11_OVERRIDE(int, Base, get); }
|
||||
};
|
||||
|
||||
struct CppDerivedVirtualOverrider : CppDerived, py::trampoline_self_life_support {
|
||||
using CppDerived::CppDerived;
|
||||
|
||||
int get() const override { PYBIND11_OVERRIDE(int, CppDerived, get); }
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
} // namespace class_sh_virtual_py_cpp_mix
|
||||
} // namespace pybind11_tests
|
||||
|
||||
using namespace pybind11_tests::class_sh_virtual_py_cpp_mix;
|
||||
|
||||
PYBIND11_SMART_HOLDER_TYPE_CASTERS(Base)
|
||||
PYBIND11_SMART_HOLDER_TYPE_CASTERS(CppDerivedPlain)
|
||||
PYBIND11_SMART_HOLDER_TYPE_CASTERS(CppDerived)
|
||||
|
||||
TEST_SUBMODULE(class_sh_virtual_py_cpp_mix, m) {
|
||||
m.attr("defined_PYBIND11_SMART_HOLDER_ENABLED") =
|
||||
#ifndef PYBIND11_SMART_HOLDER_ENABLED
|
||||
false;
|
||||
#else
|
||||
true;
|
||||
|
||||
py::classh<Base, BaseVirtualOverrider>(m, "Base").def(py::init<>()).def("get", &Base::get);
|
||||
|
||||
py::classh<CppDerivedPlain, Base>(m, "CppDerivedPlain").def(py::init<>());
|
||||
|
||||
py::classh<CppDerived, Base, CppDerivedVirtualOverrider>(m, "CppDerived").def(py::init<>());
|
||||
|
||||
m.def("get_from_cpp_plainc_ptr", get_from_cpp_plainc_ptr, py::arg("b"));
|
||||
m.def("get_from_cpp_unique_ptr", get_from_cpp_unique_ptr, py::arg("b"));
|
||||
#endif // PYBIND11_SMART_HOLDER_ENABLED
|
||||
}
|
69
tests/test_class_sh_virtual_py_cpp_mix.py
Normal file
69
tests/test_class_sh_virtual_py_cpp_mix.py
Normal file
@ -0,0 +1,69 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
from pybind11_tests import class_sh_virtual_py_cpp_mix as m
|
||||
|
||||
if not m.defined_PYBIND11_SMART_HOLDER_ENABLED:
|
||||
pytest.skip("smart_holder not available.", allow_module_level=True)
|
||||
|
||||
|
||||
class PyBase(m.Base): # Avoiding name PyDerived, for more systematic naming.
|
||||
def __init__(self):
|
||||
m.Base.__init__(self)
|
||||
|
||||
def get(self):
|
||||
return 323
|
||||
|
||||
|
||||
class PyCppDerived(m.CppDerived):
|
||||
def __init__(self):
|
||||
m.CppDerived.__init__(self)
|
||||
|
||||
def get(self):
|
||||
return 434
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("ctor", "expected"),
|
||||
[
|
||||
(m.Base, 101),
|
||||
(PyBase, 323),
|
||||
(m.CppDerivedPlain, 202),
|
||||
(m.CppDerived, 212),
|
||||
(PyCppDerived, 434),
|
||||
],
|
||||
)
|
||||
def test_base_get(ctor, expected):
|
||||
obj = ctor()
|
||||
assert obj.get() == expected
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("ctor", "expected"),
|
||||
[
|
||||
(m.Base, 4101),
|
||||
(PyBase, 4323),
|
||||
(m.CppDerivedPlain, 4202),
|
||||
(m.CppDerived, 4212),
|
||||
(PyCppDerived, 4434),
|
||||
],
|
||||
)
|
||||
def test_get_from_cpp_plainc_ptr(ctor, expected):
|
||||
obj = ctor()
|
||||
assert m.get_from_cpp_plainc_ptr(obj) == expected
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("ctor", "expected"),
|
||||
[
|
||||
(m.Base, 5101),
|
||||
(PyBase, 5323),
|
||||
(m.CppDerivedPlain, 5202),
|
||||
(m.CppDerived, 5212),
|
||||
(PyCppDerived, 5434),
|
||||
],
|
||||
)
|
||||
def test_get_from_cpp_unique_ptr(ctor, expected):
|
||||
obj = ctor()
|
||||
assert m.get_from_cpp_unique_ptr(obj) == expected
|
57
ubench/holder_comparison.cpp
Normal file
57
ubench/holder_comparison.cpp
Normal file
@ -0,0 +1,57 @@
|
||||
#include <pybind11/pybind11.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"));
|
||||
}
|
||||
|
||||
#ifdef PYBIND11_SMART_HOLDER_ENABLED
|
||||
|
||||
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.");
|
||||
|
||||
#endif
|
||||
|
||||
} // namespace hc
|
||||
|
||||
#ifdef PYBIND11_SMART_HOLDER_ENABLED
|
||||
PYBIND11_DECLARE_HOLDER_TYPE(T, hc::padded_unique_ptr<T>);
|
||||
#endif
|
||||
|
||||
PYBIND11_MODULE(pybind11_ubench_holder_comparison, m) {
|
||||
using namespace hc;
|
||||
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");
|
||||
#ifdef PYBIND11_SMART_HOLDER_ENABLED
|
||||
m.def("sizeof_smart_holder", []() { return sizeof(py::smart_holder); });
|
||||
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");
|
||||
#endif
|
||||
}
|
144
ubench/holder_comparison.py
Normal file
144
ubench/holder_comparison.py
Normal file
@ -0,0 +1,144 @@
|
||||
"""Simple comparison of holder performances, relative to unique_ptr holder."""
|
||||
|
||||
# ruff: noqa
|
||||
# This code has no unit tests.
|
||||
# ruff cleanup deferred until the next time this code is actually used.
|
||||
|
||||
import collections
|
||||
import sys
|
||||
import time
|
||||
from typing import Any, Callable, Dict, List
|
||||
|
||||
import pybind11_ubench_holder_comparison as m # type: ignore[import-not-found]
|
||||
|
||||
number_bucket_pc = None
|
||||
|
||||
|
||||
def pflush(*args: Any, **kwargs: Any) -> None:
|
||||
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.
|
||||
|
||||
|
||||
def run(args: List[str]) -> None:
|
||||
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: Callable[[int], float],
|
||||
time_delta_floor: float = 1.0e-6,
|
||||
target_elapsed_secs_multiplier: float = 1.05, # Empirical.
|
||||
target_elapsed_secs_tolerance: float = 0.05,
|
||||
max_iterations: int = 100,
|
||||
) -> int:
|
||||
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: Dict[str, List[float]] = 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) # type: ignore[misc]
|
||||
nb2 = nb_type(data_size) # type: ignore[misc]
|
||||
|
||||
def many_sum(call_repetitions: int) -> float:
|
||||
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: int) -> float:
|
||||
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): # type: ignore[unreachable]
|
||||
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}".format(
|
||||
min(rat), sum(rat) / len(rat), max(rat)
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
run(args=sys.argv[1:])
|
71
ubench/holder_comparison_extract_sheet_data.py
Normal file
71
ubench/holder_comparison_extract_sheet_data.py
Normal file
@ -0,0 +1,71 @@
|
||||
"""Extract mean ratios from holder_comparison.py output."""
|
||||
|
||||
# ruff: noqa
|
||||
# This code has no unit tests.
|
||||
# ruff cleanup deferred until the next time this code is actually used.
|
||||
|
||||
import sys
|
||||
from typing import List, Optional
|
||||
|
||||
|
||||
def run(args: List[str]) -> None:
|
||||
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: List[List[str]] = []
|
||||
|
||||
def show() -> Optional[List[str]]:
|
||||
if header_row:
|
||||
if header is None: # type: ignore[unreachable]
|
||||
print(",".join(header_row))
|
||||
else:
|
||||
assert header == header_row
|
||||
if data_row is not None:
|
||||
print(",".join(data_row)) # type: ignore[unreachable]
|
||||
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
|
||||
assert header_row is not None
|
||||
assert data_row is not None
|
||||
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
|
||||
assert header_row is not None
|
||||
assert data_row is not None
|
||||
header_row.append(line[:2])
|
||||
data_row.append(flds[2])
|
||||
show()
|
||||
|
||||
assert header_row is not None
|
||||
print("Scaled to last column:")
|
||||
print(",".join(header_row))
|
||||
for data_row in data_row_buffer:
|
||||
data_row_rescaled = data_row[:2]
|
||||
unit = float(data_row[-1])
|
||||
for fld in data_row[2:]:
|
||||
data_row_rescaled.append("%.3f" % (float(fld) / unit))
|
||||
print(",".join(data_row_rescaled))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
run(args=sys.argv[1:])
|
55
ubench/number_bucket.h
Normal file
55
ubench/number_bucket.h
Normal file
@ -0,0 +1,55 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstddef>
|
||||
#include <exception>
|
||||
#include <iostream>
|
||||
#include <vector>
|
||||
|
||||
namespace pybind11_ubench {
|
||||
|
||||
template <int Serial>
|
||||
struct number_bucket {
|
||||
std::vector<double> data;
|
||||
|
||||
explicit number_bucket(std::size_t data_size = 0) : data(data_size, 1.0) {}
|
||||
|
||||
double sum() const {
|
||||
std::size_t n = 0;
|
||||
double s = 0;
|
||||
const double *a = &*data.begin();
|
||||
const double *e = &*data.end();
|
||||
while (a != e) {
|
||||
s += *a++;
|
||||
n++;
|
||||
}
|
||||
if (n != data.size()) {
|
||||
std::cerr << "Internal consistency failure (sum)." << std::endl;
|
||||
std::terminate();
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
std::size_t add(const number_bucket &other) {
|
||||
if (other.data.size() != data.size()) {
|
||||
std::cerr << "Incompatible data sizes (add)." << std::endl;
|
||||
std::terminate();
|
||||
}
|
||||
std::size_t n = 0;
|
||||
double *a = &*data.begin();
|
||||
const double *e = &*data.end();
|
||||
const double *b = &*other.data.begin();
|
||||
while (a != e) {
|
||||
*a++ += *b++;
|
||||
n++;
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
private:
|
||||
number_bucket(const number_bucket &) = delete;
|
||||
number_bucket(number_bucket &&) = delete;
|
||||
number_bucket &operator=(const number_bucket &) = delete;
|
||||
number_bucket &operator=(number_bucket &&) = delete;
|
||||
};
|
||||
|
||||
} // namespace pybind11_ubench
|
6
ubench/python/number_bucket.clif
Normal file
6
ubench/python/number_bucket.clif
Normal file
@ -0,0 +1,6 @@
|
||||
from "pybind11/ubench/number_bucket.h":
|
||||
namespace `pybind11_ubench`:
|
||||
class `number_bucket<0>` as number_bucket_pc:
|
||||
def __init__(self, data_size: int = default)
|
||||
def sum(self) -> float
|
||||
def add(self, other: number_bucket_pc) -> int
|
Loading…
Reference in New Issue
Block a user