mirror of https://github.com/pybind/pybind11
Browse Source
* Pure `git merge --squash smart_holder` (no manual interventions). * Remove ubench/ directory. * Remove include/pybind11/smart_holder.h * [ci skip] smart_ptrs.rst updates [WIP/unfinished] * [ci skip] smart_ptrs.rst updates continued; also updating classes.rst, advanced/classes.rst * Remove README_smart_holder.rst * Restore original README.rst from master * [ci skip] Minimal change to README.rst, to leave a hint that this is pybind11v3 * [ci skip] Work in ChatGPT suggestions. * Change macro name to PYBIND11_RUN_TESTING_WITH_SMART_HOLDER_AS_DEFAULT_BUT_NEVER_USE_IN_PRODUCTION_PLEASE * Add a note pointing to the holder reinterpret_cast. * Incorporate suggestion by @virtuald: https://github.com/pybind/pybind11/pull/5542#discussion_r1967000989 * Systematically change most py::class_ to py::classh under docs/ * Remove references to README_smart_holder.rst This should have been part of commitpull/5547/headeb550d03d3
. * [ci skip] Fix minor oversight (``class_`` -> ``py::class_``) noticed by chance. * [ci skip] Resolve suggestion by @virtuald https://github.com/pybind/pybind11/pull/5542#discussion_r1969940605 * [ci skip] Apply suggestions by @timohl (thanks!) * https://github.com/pybind/pybind11/pull/5542#discussion_r1970714551 * https://github.com/pybind/pybind11/pull/5542#discussion_r1971315329 * https://github.com/pybind/pybind11/pull/5542#discussion_r1971322821 * Replace `classh : class_` inhertance with `using`, as suggested by @henryiii https://github.com/pybind/pybind11/pull/5542#issuecomment-2689034104 * Revert "Systematically change most py::class_ to py::classh under docs/" This reverts commitac9d31e13f
. * docs: focus on py::smart_holder instead of py::classh Signed-off-by: Henry Schreiner <henryschreineriii@gmail.com> * Restore minor general fixes that got lost whenac9d31e13f
was reverted. * Remove `- smart_holder` from list of branches in all .github/workflows * Extend classh note to explain whitespace noise motivation. * Suggest `py::smart_holder` for "most situations for safety" * Add back PYBIND11_HAS_INTERNALS_WITH_SMART_HOLDER_SUPPORT This define was * introduced with https://github.com/pybind/pybind11/pull/5286 * removed with https://github.com/pybind/pybind11/pull/5531 It is has been in use here: *f02a2b7653/pybind11_protobuf/native_proto_caster.h (L89-L101)
Currently pybind11 unit tests for the two holder caster backwards compatibility traits * `copyable_holder_caster_shared_ptr_with_smart_holder_support_enabled` * `move_only_holder_caster_unique_ptr_with_smart_holder_support_enabled` are missing. * Add py::trampoline_self_life_support to all trampoline examples under docs/. Address suggestion by @timohl: * https://github.com/pybind/pybind11/pull/5542#issuecomment-2686452062 Add to the "please think twice" note: the overhead for safety is likely in the noise. Also fix a two-fold inconsistency introduced by revert-commit 1e646c91b4cfd78228f7e3f6d2e0d649bad8b30a: 1. py::trampoline_self_life_support is mentioned in a note, but is missing in the example right before. 2. The section starting with To enable safely passing a ``std::unique_ptr`` to a trampoline object between is obsolete. * Fix whitespace accident (indentation) introduced with1e646c91b4
Apparently the mis-indentation was introduced when resolving merge conflicts for what became1e646c91b4
* WHITESPACE CHANGES ONLY in README.rst (list of people that made significant contributions) * Add Ethan Steinberg to list of people that made significant contributions (for completeness, unrelated to smart_holder work). * [ci skip] Add to list of people that made significant contributions: major and/or influential contributors to smart_holder branch * #2904 by @rhaschke was merged on Mar 16, 2021 * #3012 by @rhaschke was merged on May 28, 2021 * #3039 by @jakobandersen was merged on Jun 29, 2021 * #3048 by @Skylion007 was merged on Jun 18, 2021 * #3588 by @virtuald was merged on Jan 3, 2022 * #3633 by @wangxf123456 was merged on Jan 25, 2022 * #3635 by @virtuald was merged on Jan 26, 2022 * #3645 by @wangxf123456 was merged on Jan 25, 2022 * #3796 by @wangxf123456 was merged on Mar 10, 2022 * #3807 by @wangxf123456 was merged on Mar 18, 2022 * #3838 by @wangxf123456 was merged on Apr 15, 2022 * #3929 by @tomba was merged on May 7, 2022 * #4031 by @wangxf123456 was merged on Jun 27, 2022 * #4343 by @wangxf123456 was merged on Nov 18, 2022 * #4381 by @wangxf123456 was merged on Dec 5, 2022 * #4539 by @wangxf123456 was merged on Feb 28, 2023 * #4609 by @wangxf123456 was merged on Apr 6, 2023 * #4775 by @wangxf123456 was merged on Aug 3, 2023 * #4921 by @iwanders was merged on Nov 7, 2023 * #4924 by @iwanders was merged on Nov 6, 2023 * #5401 by @msimacek was merged on Oct 8, 2024 Co-authored-by: Aaron Gokaslan <aaronGokaslan@gmail.com> Co-authored-by: Dustin Spicuzza <dustin@virtualroadside.com> Co-authored-by: Ivor Wanders <iwanders@users.noreply.github.com> Co-authored-by: Jakob Lykke Andersen <Jakob@caput.dk> Co-authored-by: Michael Šimáček <michael.simacek@oracle.com> Co-authored-by: Robert Haschke <rhaschke@users.noreply.github.com> Co-authored-by: Tomi Valkeinen <tomi.valkeinen@iki.fi> Co-authored-by: Xiaofei Wang <6218006+wangxf123456@users.noreply.github.com> --------- Signed-off-by: Henry Schreiner <henryschreineriii@gmail.com> Co-authored-by: Henry Schreiner <henryschreineriii@gmail.com> Co-authored-by: Aaron Gokaslan <aaronGokaslan@gmail.com> Co-authored-by: Dustin Spicuzza <dustin@virtualroadside.com> Co-authored-by: Ivor Wanders <iwanders@users.noreply.github.com> Co-authored-by: Jakob Lykke Andersen <Jakob@caput.dk> Co-authored-by: Michael Šimáček <michael.simacek@oracle.com> Co-authored-by: Robert Haschke <rhaschke@users.noreply.github.com> Co-authored-by: Tomi Valkeinen <tomi.valkeinen@iki.fi> Co-authored-by: Xiaofei Wang <6218006+wangxf123456@users.noreply.github.com>
66 changed files with 5522 additions and 158 deletions
@ -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) |
@ -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
|
@ -0,0 +1,22 @@ |
|||||||
|
// 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 "struct_smart_holder.h" |
||||||
|
|
||||||
|
#include <type_traits> |
||||||
|
|
||||||
|
PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE) |
||||||
|
|
||||||
|
using pybindit::memory::smart_holder; |
||||||
|
|
||||||
|
PYBIND11_NAMESPACE_BEGIN(detail) |
||||||
|
|
||||||
|
template <typename H> |
||||||
|
using is_smart_holder = std::is_same<H, smart_holder>; |
||||||
|
|
||||||
|
PYBIND11_NAMESPACE_END(detail) |
||||||
|
PYBIND11_NAMESPACE_END(PYBIND11_NAMESPACE) |
@ -0,0 +1,60 @@ |
|||||||
|
// Copyright (c) 2021 The Pybind Development Team.
|
||||||
|
// All rights reserved. Use of this source code is governed by a
|
||||||
|
// BSD-style license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
#pragma once |
||||||
|
|
||||||
|
#include "detail/common.h" |
||||||
|
#include "detail/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) |
@ -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) |
@ -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
|
@ -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); |
||||||
|
} |
@ -0,0 +1,247 @@ |
|||||||
|
#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)); } |
||||||
|
|
||||||
|
TEST_SUBMODULE(class_sh_basic, m) { |
||||||
|
namespace py = pybind11; |
||||||
|
|
||||||
|
py::classh<atyp>(m, "atyp").def(py::init<>()).def(py::init([](const std::string &mtxt) { |
||||||
|
atyp obj; |
||||||
|
obj.mtxt = mtxt; |
||||||
|
return obj; |
||||||
|
})); |
||||||
|
|
||||||
|
m.def("rtrn_valu", rtrn_valu); |
||||||
|
m.def("rtrn_rref", rtrn_rref); |
||||||
|
m.def("rtrn_cref", rtrn_cref); |
||||||
|
m.def("rtrn_mref", rtrn_mref); |
||||||
|
m.def("rtrn_cptr", rtrn_cptr); |
||||||
|
m.def("rtrn_mptr", rtrn_mptr); |
||||||
|
|
||||||
|
m.def("pass_valu", pass_valu); |
||||||
|
m.def("pass_cref", pass_cref); |
||||||
|
m.def("pass_mref", pass_mref); |
||||||
|
m.def("pass_cptr", pass_cptr); |
||||||
|
m.def("pass_mptr", pass_mptr); |
||||||
|
|
||||||
|
m.def("rtrn_shmp", rtrn_shmp); |
||||||
|
m.def("rtrn_shcp", rtrn_shcp); |
||||||
|
|
||||||
|
m.def("pass_shmp", pass_shmp); |
||||||
|
m.def("pass_shcp", pass_shcp); |
||||||
|
|
||||||
|
m.def("rtrn_uqmp", rtrn_uqmp); |
||||||
|
m.def("rtrn_uqcp", rtrn_uqcp); |
||||||
|
|
||||||
|
m.def("pass_uqmp", pass_uqmp); |
||||||
|
m.def("pass_uqcp", pass_uqcp); |
||||||
|
|
||||||
|
m.def("rtrn_udmp", rtrn_udmp); |
||||||
|
m.def("rtrn_udcp", rtrn_udcp); |
||||||
|
|
||||||
|
m.def("pass_udmp", pass_udmp); |
||||||
|
m.def("pass_udcp", pass_udcp); |
||||||
|
|
||||||
|
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()); }); |
||||||
|
} |
||||||
|
|
||||||
|
} // namespace class_sh_basic
|
||||||
|
} // namespace pybind11_tests
|
@ -0,0 +1,246 @@ |
|||||||
|
# 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 |
||||||
|
|
||||||
|
|
||||||
|
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" |
@ -0,0 +1,41 @@ |
|||||||
|
#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
|
||||||
|
|
||||||
|
TEST_SUBMODULE(class_sh_disowning, m) { |
||||||
|
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); |
||||||
|
} |
@ -0,0 +1,78 @@ |
|||||||
|
from __future__ import annotations |
||||||
|
|
||||||
|
import pytest |
||||||
|
|
||||||
|
from pybind11_tests import class_sh_disowning as m |
||||||
|
|
||||||
|
|
||||||
|
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 |
||||||
|
ix = is_disowned_results.index(True) + 1 |
||||||
|
print(f"\nC++ function argument {ix} is evaluated first.") |
||||||
|
|
||||||
|
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). |
@ -0,0 +1,85 @@ |
|||||||
|
#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
|
||||||
|
|
||||||
|
TEST_SUBMODULE(class_sh_disowning_mi, m) { |
||||||
|
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); |
||||||
|
} |
@ -0,0 +1,246 @@ |
|||||||
|
from __future__ import annotations |
||||||
|
|
||||||
|
import pytest |
||||||
|
|
||||||
|
import env # noqa: F401 |
||||||
|
from pybind11_tests import class_sh_disowning_mi as m |
||||||
|
|
||||||
|
|
||||||
|
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 |
@ -0,0 +1,164 @@ |
|||||||
|
#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
|
||||||
|
|
||||||
|
TEST_SUBMODULE(class_sh_factory_constructors, m) { |
||||||
|
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.
|
||||||
|
})); |
||||||
|
} |
@ -0,0 +1,53 @@ |
|||||||
|
from __future__ import annotations |
||||||
|
|
||||||
|
import pytest |
||||||
|
|
||||||
|
from pybind11_tests import class_sh_factory_constructors as m |
||||||
|
|
||||||
|
|
||||||
|
def test_atyp_factories(): |
||||||
|
assert m.atyp_valu().get_mtxt() == "Valu" |
||||||
|
assert m.atyp_rref().get_mtxt() == "Rref" |
||||||
|
# sert m.atyp_cref().get_mtxt() == "Cref" |
||||||
|
# sert m.atyp_mref().get_mtxt() == "Mref" |
||||||
|
# sert m.atyp_cptr().get_mtxt() == "Cptr" |
||||||
|
assert m.atyp_mptr().get_mtxt() == "Mptr" |
||||||
|
assert m.atyp_shmp().get_mtxt() == "Shmp" |
||||||
|
# sert m.atyp_shcp().get_mtxt() == "Shcp" |
||||||
|
assert m.atyp_uqmp().get_mtxt() == "Uqmp" |
||||||
|
# sert m.atyp_uqcp().get_mtxt() == "Uqcp" |
||||||
|
assert m.atyp_udmp().get_mtxt() == "Udmp" |
||||||
|
# sert m.atyp_udcp().get_mtxt() == "Udcp" |
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize( |
||||||
|
("init_args", "expected"), |
||||||
|
[ |
||||||
|
((3,), 300), |
||||||
|
((5, 7), 570), |
||||||
|
((9, 11, 13), 1023), |
||||||
|
], |
||||||
|
) |
||||||
|
def test_with_alias_success(init_args, expected): |
||||||
|
assert m.with_alias(*init_args).val == expected |
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize( |
||||||
|
("num_init_args", "smart_ptr"), |
||||||
|
[ |
||||||
|
(4, "std::unique_ptr"), |
||||||
|
(5, "std::shared_ptr"), |
||||||
|
], |
||||||
|
) |
||||||
|
def test_with_alias_invalid(num_init_args, smart_ptr): |
||||||
|
class PyDrvdWithAlias(m.with_alias): |
||||||
|
pass |
||||||
|
|
||||||
|
with pytest.raises(TypeError) as excinfo: |
||||||
|
PyDrvdWithAlias(*((0,) * num_init_args)) |
||||||
|
assert ( |
||||||
|
str(excinfo.value) |
||||||
|
== "pybind11::init(): construction failed: returned " |
||||||
|
+ smart_ptr |
||||||
|
+ " pointee is not an alias instance" |
||||||
|
) |
@ -0,0 +1,90 @@ |
|||||||
|
#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
|
||||||
|
|
||||||
|
TEST_SUBMODULE(class_sh_inheritance, m) { |
||||||
|
py::classh<base>(m, "base"); |
||||||
|
py::classh<drvd, base>(m, "drvd"); |
||||||
|
|
||||||
|
auto rvto = py::return_value_policy::take_ownership; |
||||||
|
|
||||||
|
m.def("rtrn_mptr_drvd", rtrn_mptr_drvd, rvto); |
||||||
|
m.def("rtrn_mptr_drvd_up_cast", rtrn_mptr_drvd_up_cast, rvto); |
||||||
|
m.def("pass_cptr_base", pass_cptr_base); |
||||||
|
m.def("pass_cptr_drvd", pass_cptr_drvd); |
||||||
|
|
||||||
|
m.def("rtrn_shmp_drvd", rtrn_shmp_drvd); |
||||||
|
m.def("rtrn_shmp_drvd_up_cast", rtrn_shmp_drvd_up_cast); |
||||||
|
m.def("pass_shcp_base", pass_shcp_base); |
||||||
|
m.def("pass_shcp_drvd", pass_shcp_drvd); |
||||||
|
|
||||||
|
// __init__ needed for Python inheritance.
|
||||||
|
py::classh<base1>(m, "base1").def(py::init<>()); |
||||||
|
py::classh<base2>(m, "base2").def(py::init<>()); |
||||||
|
py::classh<drvd2, base1, base2>(m, "drvd2"); |
||||||
|
|
||||||
|
m.def("rtrn_mptr_drvd2", rtrn_mptr_drvd2, rvto); |
||||||
|
m.def("rtrn_mptr_drvd2_up_cast1", rtrn_mptr_drvd2_up_cast1, rvto); |
||||||
|
m.def("rtrn_mptr_drvd2_up_cast2", rtrn_mptr_drvd2_up_cast2, rvto); |
||||||
|
m.def("pass_cptr_base1", pass_cptr_base1); |
||||||
|
m.def("pass_cptr_base2", pass_cptr_base2); |
||||||
|
m.def("pass_cptr_drvd2", pass_cptr_drvd2); |
||||||
|
} |
||||||
|
|
||||||
|
} // namespace class_sh_inheritance
|
||||||
|
} // namespace pybind11_tests
|
@ -0,0 +1,63 @@ |
|||||||
|
from __future__ import annotations |
||||||
|
|
||||||
|
from pybind11_tests import class_sh_inheritance as m |
||||||
|
|
||||||
|
|
||||||
|
def test_rtrn_mptr_drvd_pass_cptr_base(): |
||||||
|
d = m.rtrn_mptr_drvd() |
||||||
|
i = m.pass_cptr_base(d) # load_impl Case 2a |
||||||
|
assert i == 2 * 100 + 11 |
||||||
|
|
||||||
|
|
||||||
|
def test_rtrn_shmp_drvd_pass_shcp_base(): |
||||||
|
d = m.rtrn_shmp_drvd() |
||||||
|
i = m.pass_shcp_base(d) # load_impl Case 2a |
||||||
|
assert i == 2 * 100 + 21 |
||||||
|
|
||||||
|
|
||||||
|
def test_rtrn_mptr_drvd_up_cast_pass_cptr_drvd(): |
||||||
|
b = m.rtrn_mptr_drvd_up_cast() |
||||||
|
# the base return is down-cast immediately. |
||||||
|
assert b.__class__.__name__ == "drvd" |
||||||
|
i = m.pass_cptr_drvd(b) |
||||||
|
assert i == 2 * 100 + 12 |
||||||
|
|
||||||
|
|
||||||
|
def test_rtrn_shmp_drvd_up_cast_pass_shcp_drvd(): |
||||||
|
b = m.rtrn_shmp_drvd_up_cast() |
||||||
|
# the base return is down-cast immediately. |
||||||
|
assert b.__class__.__name__ == "drvd" |
||||||
|
i = m.pass_shcp_drvd(b) |
||||||
|
assert i == 2 * 100 + 22 |
||||||
|
|
||||||
|
|
||||||
|
def test_rtrn_mptr_drvd2_pass_cptr_bases(): |
||||||
|
d = m.rtrn_mptr_drvd2() |
||||||
|
i1 = m.pass_cptr_base1(d) # load_impl Case 2c |
||||||
|
assert i1 == 3 * 110 + 4 * 120 + 21 |
||||||
|
i2 = m.pass_cptr_base2(d) |
||||||
|
assert i2 == 3 * 110 + 4 * 120 + 22 |
||||||
|
|
||||||
|
|
||||||
|
def test_rtrn_mptr_drvd2_up_casts_pass_cptr_drvd2(): |
||||||
|
b1 = m.rtrn_mptr_drvd2_up_cast1() |
||||||
|
assert b1.__class__.__name__ == "drvd2" |
||||||
|
i1 = m.pass_cptr_drvd2(b1) |
||||||
|
assert i1 == 3 * 110 + 4 * 120 + 23 |
||||||
|
b2 = m.rtrn_mptr_drvd2_up_cast2() |
||||||
|
assert b2.__class__.__name__ == "drvd2" |
||||||
|
i2 = m.pass_cptr_drvd2(b2) |
||||||
|
assert i2 == 3 * 110 + 4 * 120 + 23 |
||||||
|
|
||||||
|
|
||||||
|
def test_python_drvd2(): |
||||||
|
class Drvd2(m.base1, m.base2): |
||||||
|
def __init__(self): |
||||||
|
m.base1.__init__(self) |
||||||
|
m.base2.__init__(self) |
||||||
|
|
||||||
|
d = Drvd2() |
||||||
|
i1 = m.pass_cptr_base1(d) # load_impl Case 2b |
||||||
|
assert i1 == 110 + 21 |
||||||
|
i2 = m.pass_cptr_base2(d) |
||||||
|
assert i2 == 120 + 22 |
@ -0,0 +1,93 @@ |
|||||||
|
#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
|
||||||
|
|
||||||
|
TEST_SUBMODULE(class_sh_mi_thunks, m) { |
||||||
|
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(); |
||||||
|
}); |
||||||
|
} |
@ -0,0 +1,53 @@ |
|||||||
|
from __future__ import annotations |
||||||
|
|
||||||
|
import pytest |
||||||
|
|
||||||
|
from pybind11_tests import class_sh_mi_thunks as m |
||||||
|
|
||||||
|
|
||||||
|
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)." |
||||||
|
) |
@ -0,0 +1,94 @@ |
|||||||
|
// 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_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
|
||||||
|
|
||||||
|
TEST_SUBMODULE(class_sh_property, m) { |
||||||
|
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); |
||||||
|
} |
@ -0,0 +1,166 @@ |
|||||||
|
# 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 |
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.skipif( |
||||||
|
"env.PYPY or env.GRAALPY", 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*" |
@ -0,0 +1,63 @@ |
|||||||
|
#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; |
||||||
|
|
||||||
|
TEST_SUBMODULE(class_sh_property_non_owning, m) { |
||||||
|
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); |
||||||
|
} |
@ -0,0 +1,30 @@ |
|||||||
|
from __future__ import annotations |
||||||
|
|
||||||
|
import pytest |
||||||
|
|
||||||
|
from pybind11_tests import class_sh_property_non_owning as m |
||||||
|
|
||||||
|
|
||||||
|
@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 |
@ -0,0 +1,103 @@ |
|||||||
|
#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
|
||||||
|
|
||||||
|
TEST_SUBMODULE(class_sh_shared_ptr_copy_move, m) { |
||||||
|
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; |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
} // namespace pybind11_tests
|
@ -0,0 +1,41 @@ |
|||||||
|
from __future__ import annotations |
||||||
|
|
||||||
|
from pybind11_tests import class_sh_shared_ptr_copy_move as m |
||||||
|
|
||||||
|
|
||||||
|
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) |
@ -0,0 +1,82 @@ |
|||||||
|
#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); |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
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); |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
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; |
||||||
|
} |
||||||
|
|
||||||
|
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")); |
||||||
|
} |
||||||
|
|
||||||
|
} // namespace class_sh_trampoline_basic
|
||||||
|
} // namespace pybind11_tests
|
||||||
|
|
||||||
|
using namespace pybind11_tests::class_sh_trampoline_basic; |
||||||
|
|
||||||
|
TEST_SUBMODULE(class_sh_trampoline_basic, m) { |
||||||
|
wrap<0>(m, "Abase0"); |
||||||
|
wrap<1>(m, "Abase1"); |
||||||
|
} |
@ -0,0 +1,59 @@ |
|||||||
|
from __future__ import annotations |
||||||
|
|
||||||
|
import pytest |
||||||
|
|
||||||
|
from pybind11_tests import class_sh_trampoline_basic as m |
||||||
|
|
||||||
|
|
||||||
|
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). |
@ -0,0 +1,86 @@ |
|||||||
|
// 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/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"} {} |
||||||
|
}; |
||||||
|
|
||||||
|
struct Big5Trampoline : Big5, py::trampoline_self_life_support { |
||||||
|
using Big5::Big5; |
||||||
|
}; |
||||||
|
|
||||||
|
} // namespace class_sh_trampoline_self_life_support
|
||||||
|
} // namespace pybind11_tests
|
||||||
|
|
||||||
|
using namespace pybind11_tests::class_sh_trampoline_self_life_support; |
||||||
|
|
||||||
|
TEST_SUBMODULE(class_sh_trampoline_self_life_support, m) { |
||||||
|
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); |
||||||
|
}); |
||||||
|
} |
@ -0,0 +1,38 @@ |
|||||||
|
from __future__ import annotations |
||||||
|
|
||||||
|
import pytest |
||||||
|
|
||||||
|
import pybind11_tests.class_sh_trampoline_self_life_support as m |
||||||
|
|
||||||
|
|
||||||
|
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 |
@ -0,0 +1,137 @@ |
|||||||
|
// 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_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; |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
struct SftTrampoline : Sft, py::trampoline_self_life_support { |
||||||
|
using Sft::Sft; |
||||||
|
}; |
||||||
|
|
||||||
|
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; |
||||||
|
|
||||||
|
TEST_SUBMODULE(class_sh_trampoline_shared_from_this, m) { |
||||||
|
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); |
||||||
|
} |
@ -0,0 +1,247 @@ |
|||||||
|
from __future__ import annotations |
||||||
|
|
||||||
|
import sys |
||||||
|
import weakref |
||||||
|
|
||||||
|
import pytest |
||||||
|
|
||||||
|
import env |
||||||
|
import pybind11_tests.class_sh_trampoline_shared_from_this as m |
||||||
|
|
||||||
|
|
||||||
|
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 and not env.GRAALPY: |
||||||
|
assert obj0_wr() is not None |
||||||
|
del obj # This releases the chain recursively. |
||||||
|
if not env.PYPY and not env.GRAALPY: |
||||||
|
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." |
||||||
|
) |
@ -0,0 +1,92 @@ |
|||||||
|
// 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_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; |
||||||
|
|
||||||
|
TEST_SUBMODULE(class_sh_trampoline_shared_ptr_cpp_arg, m) { |
||||||
|
// 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); |
||||||
|
} |
@ -0,0 +1,154 @@ |
|||||||
|
from __future__ import annotations |
||||||
|
|
||||||
|
import pytest |
||||||
|
|
||||||
|
import env # noqa: F401 |
||||||
|
import pybind11_tests.class_sh_trampoline_shared_ptr_cpp_arg as m |
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.skipif("env.GRAALPY", reason="Cannot reliably trigger GC") |
||||||
|
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() |
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.skipif("env.GRAALPY", reason="Cannot reliably trigger GC") |
||||||
|
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 |
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.skipif("env.GRAALPY", reason="Cannot reliably trigger GC") |
||||||
|
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 |
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.skipif("env.GRAALPY", reason="Cannot reliably trigger GC") |
||||||
|
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 |
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.skipif("env.GRAALPY", reason="Cannot reliably trigger GC") |
||||||
|
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). |
@ -0,0 +1,63 @@ |
|||||||
|
// 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/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
|
||||||
|
|
||||||
|
namespace pybind11_tests { |
||||||
|
namespace class_sh_trampoline_unique_ptr { |
||||||
|
|
||||||
|
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); } |
||||||
|
}; |
||||||
|
|
||||||
|
} // namespace class_sh_trampoline_unique_ptr
|
||||||
|
} // namespace pybind11_tests
|
||||||
|
|
||||||
|
TEST_SUBMODULE(class_sh_trampoline_unique_ptr, m) { |
||||||
|
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(); }); |
||||||
|
} |
@ -0,0 +1,31 @@ |
|||||||
|
from __future__ import annotations |
||||||
|
|
||||||
|
import pybind11_tests.class_sh_trampoline_unique_ptr as m |
||||||
|
|
||||||
|
|
||||||
|
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). |
@ -0,0 +1,30 @@ |
|||||||
|
#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) {} |
||||||
|
}; |
||||||
|
|
||||||
|
TEST_SUBMODULE(class_sh_unique_ptr_custom_deleter, m) { |
||||||
|
py::classh<Pet>(m, "Pet").def_readwrite("name", &Pet::name); |
||||||
|
|
||||||
|
m.def("create", &Pet::New); |
||||||
|
} |
||||||
|
|
||||||
|
} // namespace class_sh_unique_ptr_custom_deleter
|
||||||
|
} // namespace pybind11_tests
|
@ -0,0 +1,8 @@ |
|||||||
|
from __future__ import annotations |
||||||
|
|
||||||
|
from pybind11_tests import class_sh_unique_ptr_custom_deleter as m |
||||||
|
|
||||||
|
|
||||||
|
def test_create(): |
||||||
|
pet = m.create("abc") |
||||||
|
assert pet.name == "abc" |
@ -0,0 +1,50 @@ |
|||||||
|
#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_; |
||||||
|
}; |
||||||
|
|
||||||
|
TEST_SUBMODULE(class_sh_unique_ptr_member, m) { |
||||||
|
py::classh<pointee>(m, "pointee").def(py::init<>()).def("get_int", &pointee::get_int); |
||||||
|
|
||||||
|
m.def("make_unique_pointee", make_unique_pointee); |
||||||
|
|
||||||
|
py::class_<ptr_owner>(m, "ptr_owner") |
||||||
|
.def(py::init<std::unique_ptr<pointee>>(), py::arg("ptr")) |
||||||
|
.def("is_owner", &ptr_owner::is_owner) |
||||||
|
.def("give_up_ownership_via_unique_ptr", &ptr_owner::give_up_ownership_via_unique_ptr) |
||||||
|
.def("give_up_ownership_via_shared_ptr", &ptr_owner::give_up_ownership_via_shared_ptr); |
||||||
|
} |
||||||
|
|
||||||
|
} // namespace class_sh_unique_ptr_member
|
||||||
|
} // namespace pybind11_tests
|
@ -0,0 +1,26 @@ |
|||||||
|
from __future__ import annotations |
||||||
|
|
||||||
|
import pytest |
||||||
|
|
||||||
|
from pybind11_tests import class_sh_unique_ptr_member as m |
||||||
|
|
||||||
|
|
||||||
|
def test_make_unique_pointee(): |
||||||
|
obj = m.make_unique_pointee() |
||||||
|
assert obj.get_int() == 213 |
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize( |
||||||
|
"give_up_ownership_via", |
||||||
|
["give_up_ownership_via_unique_ptr", "give_up_ownership_via_shared_ptr"], |
||||||
|
) |
||||||
|
def test_pointee_and_ptr_owner(give_up_ownership_via): |
||||||
|
obj = m.pointee() |
||||||
|
assert obj.get_int() == 213 |
||||||
|
owner = m.ptr_owner(obj) |
||||||
|
with pytest.raises(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 |
@ -0,0 +1,58 @@ |
|||||||
|
#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; } |
||||||
|
|
||||||
|
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); } |
||||||
|
}; |
||||||
|
|
||||||
|
} // namespace class_sh_virtual_py_cpp_mix
|
||||||
|
} // namespace pybind11_tests
|
||||||
|
|
||||||
|
using namespace pybind11_tests::class_sh_virtual_py_cpp_mix; |
||||||
|
|
||||||
|
TEST_SUBMODULE(class_sh_virtual_py_cpp_mix, m) { |
||||||
|
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")); |
||||||
|
} |
@ -0,0 +1,66 @@ |
|||||||
|
from __future__ import annotations |
||||||
|
|
||||||
|
import pytest |
||||||
|
|
||||||
|
from pybind11_tests import class_sh_virtual_py_cpp_mix as m |
||||||
|
|
||||||
|
|
||||||
|
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 |
Loading…
Reference in new issue