From 68c08f99b98663f80da22911387cbf9e1ff1f5ed Mon Sep 17 00:00:00 2001 From: Jean-Louis Leroy Date: Sat, 5 Sep 2026 12:23:33 -0400 Subject: [PATCH 1/3] interop: add weak_virtual_ptr for std::weak_ptr Closes #73. Add , providing weak_virtual_ptr, an alias for virtual_ptr>. It tracks an object with a std::weak_ptr and remembers its v-table pointer, so that lock() returns a shared_virtual_ptr without a hash table lookup. Remembering the vptr is safe: the weak pointer keeps the control block alive, so once the object is destroyed it stays expired for good. It is a storage facility only. It is constructed from a shared_virtual_ptr (or a std::shared_ptr or std::weak_ptr), converts to a weak_virtual_ptr to a base class, and offers lock(), expired(), use_count(), reset(), pointer() and vptr(). It cannot be dereferenced, and it cannot be used as a virtual parameter - neither as virtual_> nor as weak_virtual_ptr - because the object may no longer exist; validate_method_parameter specializations reject both with "a weak pointer cannot be a virtual parameter; call lock() first". std::weak_ptr does not fit the generic smart-pointer specialization of virtual_ptr, which needs get(), operator* and a conversion to bool, so the specialization is written by hand. virtual_traits is deliberately not specialized for std::weak_ptr: that keeps IsSmartPtr false, which is what lets the hand-written specialization win. In core.hpp, the plain virtual_ptr's converting constructor and assignment from another virtual_ptr now also require the source to have get() (detail::has_get), so a weak source is a clean substitution failure instead of a hard error in the body, and is_constructible reports it correctly. Co-Authored-By: Claude Opus 5 --- doc/modules/ROOT/pages/ref_headers.adoc | 11 + doc/modules/ROOT/pages/smart_pointers.adoc | 41 ++ doc/modules/ROOT/snippets/smart_pointers.cpp | 33 ++ include/boost/openmethod/core.hpp | 20 +- .../boost/openmethod/interop/std_weak_ptr.hpp | 455 ++++++++++++++++++ test/compile_fail_weak_ptr_parameter.cpp | 29 ++ ...ompile_fail_weak_virtual_ptr_parameter.cpp | 29 ++ test/test_weak_virtual_ptr.cpp | 313 ++++++++++++ 8 files changed, 927 insertions(+), 4 deletions(-) create mode 100644 include/boost/openmethod/interop/std_weak_ptr.hpp create mode 100644 test/compile_fail_weak_ptr_parameter.cpp create mode 100644 test/compile_fail_weak_virtual_ptr_parameter.cpp create mode 100644 test/test_weak_virtual_ptr.cpp diff --git a/doc/modules/ROOT/pages/ref_headers.adoc b/doc/modules/ROOT/pages/ref_headers.adoc index 3003bb6b..0cde474d 100644 --- a/doc/modules/ROOT/pages/ref_headers.adoc +++ b/doc/modules/ROOT/pages/ref_headers.adoc @@ -29,6 +29,9 @@ parameters: * xref:#std_unique_ptr[``] to use `std::unique_ptr` in virtual parameters. +* xref:#std_weak_ptr[``] to track +objects with `std::weak_ptr` without losing their v-table pointer. + ## High-level Headers [#core] @@ -72,6 +75,14 @@ Provides a `virtual_traits` specialization that makes it possible to use a Provides a `virtual_traits` specialization that makes it possible to use a `std::unique_ptr` in place of a raw pointer or reference in virtual parameters. +[#std_weak_ptr] +### link:{headers-url}/boost/openmethod/interop/std_weak_ptr.hpp[] + +Provides cpp:weak_virtual_ptr[], a `virtual_ptr` that tracks an object with a +`std::weak_ptr` and remembers its v-table pointer. It cannot be used in virtual +parameters; its `lock` function returns a cpp:shared_virtual_ptr[], without a +hash table lookup. + [#boost_intrusive_ptr] ### link:{headers-url}/boost/openmethod/interop/boost_intrusive_ptr.hpp[] diff --git a/doc/modules/ROOT/pages/smart_pointers.adoc b/doc/modules/ROOT/pages/smart_pointers.adoc index bd19e994..3231ae16 100644 --- a/doc/modules/ROOT/pages/smart_pointers.adoc +++ b/doc/modules/ROOT/pages/smart_pointers.adoc @@ -67,6 +67,47 @@ in ``, and support for ``. `` does not include smart pointer headers, so they must be included explicitly. +[#weak_pointers] +## Weak Pointers + +A `std::weak_ptr` observes an object without keeping it alive. Since the object +may be gone, there is nothing to dispatch on: a weak pointer cannot be used in a +virtual parameter, and neither can a `virtual_ptr` to a weak pointer. Still, an +object that is tracked by weak pointers - in a cache, an observer list, or a +back pointer - is typically an object that methods will be called on, once a +weak pointer has been locked. + +- cpp:weak_virtual_ptr[] is an alias for `virtual_ptr>` + +A `weak_virtual_ptr` is a storage facility. It is constructed from a +`shared_virtual_ptr` (or from a `std::shared_ptr` or a `std::weak_ptr`), and +it remembers the v-table pointer along with the weak pointer. It cannot be +dereferenced. Its `lock` function returns a `shared_virtual_ptr`, which can be +passed to methods. Since the v-table pointer is copied, not looked up, `lock` +costs no more than `std::weak_ptr::lock`. It returns an empty +`shared_virtual_ptr` if the object no longer exists. + +[source,c++] +---- +shared_virtual_ptr animal = make_shared_virtual(); +weak_virtual_ptr observer = animal; + +std::cout << poke(observer.lock()) << "\n"; // bark + +animal = nullptr; +std::cout << std::boolalpha << observer.expired() << "\n"; // true +---- + +Remembering the v-table pointer is safe because a `std::weak_ptr` keeps the +control block alive: once the object has been destroyed, the weak pointer stays +expired, and the v-table pointer can never be applied to another object. + +A `weak_virtual_ptr` converts to a `weak_virtual_ptr` to a base class, but not +to a plain or a shared `virtual_ptr`. A cast to a derived class requires the +object: use `lock`, then `cast`. Support for `std::weak_ptr` is provided in +``, which also includes the +`std::shared_ptr` header. + Here is a variation of the AST example that uses dynamic allocation and unique pointers: diff --git a/doc/modules/ROOT/snippets/smart_pointers.cpp b/doc/modules/ROOT/snippets/smart_pointers.cpp index 264a8e41..c8b11f70 100644 --- a/doc/modules/ROOT/snippets/smart_pointers.cpp +++ b/doc/modules/ROOT/snippets/smart_pointers.cpp @@ -7,6 +7,7 @@ #include #include #include +#include #define BOOST_TEST_MODULE openmethod #include @@ -194,3 +195,35 @@ BOOST_AUTO_TEST_CASE(unique_ptr_examples) { BOOST_TEST(cout.str() == "bark\nhiss\n"); } } + +BOOST_AUTO_TEST_CASE(weak_ptr_examples) { + initialize(); + + { + using namespace shared_vptr; + capture_cout cout; + + // tag::weak_lock[] + shared_virtual_ptr animal = make_shared_virtual(); + weak_virtual_ptr observer = animal; + + std::cout << poke(observer.lock()) << "\n"; // bark + + animal = nullptr; + std::cout << std::boolalpha << observer.expired() << "\n"; // true + // end::weak_lock[] + + BOOST_TEST(cout.str() == "bark\ntrue\n"); + } + + { + // tag::weak_virtual_ptr_alias[] + shared_virtual_ptr animal = make_shared_virtual(); + weak_virtual_ptr observer = animal; + std::weak_ptr weak = observer.pointer(); + + BOOST_TEST(animal.pointer().use_count() == 1); + BOOST_TEST(weak.lock() == animal.pointer()); + // end::weak_virtual_ptr_alias[] + } +} diff --git a/include/boost/openmethod/core.hpp b/include/boost/openmethod/core.hpp index ee21b262..c2417e7a 100644 --- a/include/boost/openmethod/core.hpp +++ b/include/boost/openmethod/core.hpp @@ -828,6 +828,13 @@ struct same_smart_ptr_aux< typename virtual_traits::template rebind< typename Other::element_type>> {}; +template +constexpr bool has_get = false; + +template +constexpr bool + has_get().get())>> = true; + } // namespace detail BOOST_OPENMETHOD_OPEN_NAMESPACE_DETAIL_UNLESS_MRDOCS @@ -1249,8 +1256,10 @@ class virtual_ptr { //! @li @c Other's object pointer must be assignable to a @c Class*. template< class Other, - typename = std::enable_if_t::element_type*>>> + typename = std::enable_if_t< + std::is_constructible_v< + Class*, typename virtual_ptr::element_type*> && + detail::has_get>>> virtual_ptr(const virtual_ptr& other) : vp(other.vp), obj(other.get()) { } @@ -1357,8 +1366,11 @@ class virtual_ptr { //! @li @c Other's object pointer must be assignable to a @c Class*. template< class Other, - typename = std::enable_if_t::element_type*>>> + typename = std::enable_if_t< + std::is_assignable_v< + Class*&, + typename virtual_ptr::element_type*> && + detail::has_get>>> virtual_ptr& operator=(const virtual_ptr& other) { obj = other.get(); vp = other.vp; diff --git a/include/boost/openmethod/interop/std_weak_ptr.hpp b/include/boost/openmethod/interop/std_weak_ptr.hpp new file mode 100644 index 00000000..a554aed5 --- /dev/null +++ b/include/boost/openmethod/interop/std_weak_ptr.hpp @@ -0,0 +1,455 @@ +// Copyright (c) 2017-2026 Jean-Louis Leroy +// Distributed under the Boost Software License, Version 1.0. +// See accompanying file LICENSE_1_0.txt +// or copy at http://www.boost.org/LICENSE_1_0.txt) + +#ifndef BOOST_OPENMETHOD_INTEROP_WEAK_PTR_HPP +#define BOOST_OPENMETHOD_INTEROP_WEAK_PTR_HPP + +#include +#include + +namespace boost::openmethod { +namespace detail { + +// A weak pointer may refer to an object that no longer exists, so there is +// nothing to dispatch on. `virtual_traits` is deliberately *not* specialized +// for `std::weak_ptr`: that keeps `IsSmartPtr` false, which is what lets the +// hand-written `virtual_ptr` specialization below win over the generic smart +// pointer one. These specializations only replace the vague diagnostics that +// would result from using a weak pointer as a virtual parameter with a useful +// one. + +template +struct validate_method_parameter>, Registry, void> : + std::false_type { + static_assert( + false_t, + "a weak pointer cannot be a virtual parameter; call lock() first"); +}; + +template +struct validate_method_parameter< + virtual_ptr, Registry>, Registry, void> : std::false_type { + static_assert( + false_t, + "a weak pointer cannot be a virtual parameter; call lock() first"); +}; + +template +struct validate_method_parameter< + virtual_ptr, Registry>&, Registry, void> : + std::false_type { + static_assert( + false_t, + "a weak pointer cannot be a virtual parameter; call lock() first"); +}; + +template +struct validate_method_parameter< + const virtual_ptr, Registry>&, Registry, void> : + std::false_type { + static_assert( + false_t, + "a weak pointer cannot be a virtual parameter; call lock() first"); +}; + +} // namespace detail + +//! Wide pointer combining a `std::weak_ptr` to an object and a pointer to its +//! v-table +//! +//! This specialization of `virtual_ptr` tracks the object with a +//! `std::weak_ptr`, and remembers its v-table pointer. It is a storage +//! facility: unlike other kinds of `virtual_ptr`, it cannot be dereferenced, +//! and it cannot be used as a virtual parameter, because the object may no +//! longer exist. Instead, call `lock()` to obtain a @ref shared_virtual_ptr, +//! then use it as usual. Since the v-table pointer is copied from the weak +//! pointer, `lock()` costs no more than `std::weak_ptr::lock()`: no hash table +//! lookup is needed. +//! +//! Remembering the v-table pointer is safe: a `std::weak_ptr` keeps the +//! control block alive, so once the object is destroyed, the weak pointer +//! stays expired. The v-table pointer can never be applied to another object. +//! +//! @par Example +//! include:smart_pointers.cpp#classes;weak_lock +//! +//! @tparam Class The class of the object, possibly cv-qualified +//! @tparam Registry The registry in which `Class` is registered +//! +//! @see @ref weak_virtual_ptr +//! @see [Smart Pointers](xref:ROOT:smart_pointers.adoc) +template +class virtual_ptr, Registry, void> { +#ifndef __MRDOCS__ + template + friend class virtual_ptr; +#endif + + static constexpr bool use_indirect_vptrs = Registry::has_indirect_vptr; + + std::conditional_t vp; + std::weak_ptr obj; + + template + static auto vptr_of(const std::shared_ptr& other) { + return detail::box_vptr( + other ? detail::acquire_vptr(*other) : detail::null_vptr); + } + + public: + //! Class pointed to by the `std::weak_ptr` + using element_type = Class; + + //! Default constructor + //! + //! Construct an empty `std::weak_ptr`. Set the v-table pointer to + //! `nullptr`. + virtual_ptr() : + vp(detail::box_vptr(detail::null_vptr)) { + } + + //! Construct from `nullptr` + //! + //! Construct an empty `std::weak_ptr`. Set the v-table pointer to + //! `nullptr`. + //! + //! @param value A `nullptr`. + explicit virtual_ptr(std::nullptr_t) : + vp(detail::box_vptr(detail::null_vptr)) { + } + + virtual_ptr(const virtual_ptr& other) = default; + + virtual_ptr(virtual_ptr&& other) : + vp(std::exchange( + other.vp, detail::box_vptr(detail::null_vptr))), + obj(std::move(other.obj)) { + } + + //! Construct from a `shared_virtual_ptr` to a derived class + //! + //! Copy the v-table pointer from `other`. Construct the `std::weak_ptr` + //! from the `std::shared_ptr` held by `other`. + //! + //! `Other` is _not_ required to be a polymorphic class: the v-table + //! pointer is already known. + //! + //! @par Example + //! include:smart_pointers.cpp#classes;weak_lock + //! + //! @param other A `shared_virtual_ptr` to an object of a class derived from + //! `Class`. + //! + //! @par Requirements + //! @li `std::weak_ptr` must be constructible from + //! `const std::shared_ptr&`. + template< + class Other, + typename = std::enable_if_t, const std::shared_ptr&>>> + virtual_ptr(const virtual_ptr, Registry>& other) : + vp(other.vp), obj(other.obj) { + } + + //! Construct from a `weak_virtual_ptr` to a derived class + //! + //! Copy the v-table pointer and the `std::weak_ptr` from `other`. + //! + //! @param other A `weak_virtual_ptr` to an object of a class derived from + //! `Class`. + //! + //! @par Requirements + //! @li `std::weak_ptr` must be constructible from + //! `const std::weak_ptr&`. + template< + class Other, + typename = std::enable_if_t, const std::weak_ptr&>>> + virtual_ptr(const virtual_ptr, Registry>& other) : + vp(other.vp), obj(other.obj) { + } + + //! Move-construct from a `weak_virtual_ptr` to a derived class + //! + //! Copy the v-table pointer from `other`, and set it to `nullptr` in + //! `other`. Move the `std::weak_ptr` from `other`. + //! + //! @param other A `weak_virtual_ptr` to an object of a class derived from + //! `Class`. + //! + //! @par Requirements + //! @li `std::weak_ptr` must be constructible from + //! `std::weak_ptr&&`. + template< + class Other, + typename = std::enable_if_t, std::weak_ptr&&>>> + virtual_ptr(virtual_ptr, Registry>&& other) : + vp(std::exchange( + other.vp, detail::box_vptr(detail::null_vptr))), + obj(std::move(other.obj)) { + } + + //! Construct from a `std::shared_ptr` to a derived class + //! + //! Construct the `std::weak_ptr` from `other`. Set the v-table pointer + //! according to the dynamic type of `*other`. + //! + //! @param other A `std::shared_ptr` to a polymorphic object. + //! + //! @par Requirements + //! @li `Other` must be a polymorphic class, according to the `rtti` + //! policy of `Registry`. + //! @li `std::weak_ptr` must be constructible from + //! `const std::shared_ptr&`. + template< + class Other, + typename = std::enable_if_t< + BOOST_OPENMETHOD_UNLESS_MRDOCS(detail::) + IsPolymorphic && + std::is_constructible_v< + std::weak_ptr, const std::shared_ptr&>>> + virtual_ptr(const std::shared_ptr& other) : + vp(vptr_of(other)), obj(other) { + } + + //! Construct from a `std::weak_ptr` to a derived class + //! + //! Construct the `std::weak_ptr` from `other`. Lock `other` to find the + //! dynamic type of the object, and set the v-table pointer accordingly. If + //! `other` has expired, the v-table pointer is set to `nullptr`. + //! + //! @param other A `std::weak_ptr` to a polymorphic object. + //! + //! @par Requirements + //! @li `Other` must be a polymorphic class, according to the `rtti` + //! policy of `Registry`. + //! @li `std::weak_ptr` must be constructible from + //! `const std::weak_ptr&`. + template< + class Other, + typename = std::enable_if_t< + BOOST_OPENMETHOD_UNLESS_MRDOCS(detail::) + IsPolymorphic && + std::is_constructible_v< + std::weak_ptr, const std::weak_ptr&>>> + virtual_ptr(const std::weak_ptr& other) : + vp(vptr_of(other.lock())), obj(other) { + } + + //! Assign from `nullptr` + //! + //! Reset the `std::weak_ptr`. Set the v-table pointer to `nullptr`. + //! + //! @param value A `nullptr`. + virtual_ptr& operator=(std::nullptr_t) { + obj.reset(); + vp = detail::box_vptr(detail::null_vptr); + return *this; + } + + virtual_ptr& operator=(const virtual_ptr& other) = default; + + virtual_ptr& operator=(virtual_ptr&& other) { + vp = std::exchange( + other.vp, detail::box_vptr(detail::null_vptr)); + obj = std::move(other.obj); + return *this; + } + + //! Assign from a `shared_virtual_ptr` to a derived class + //! + //! Copy the v-table pointer from `other`. Assign the `std::weak_ptr` from + //! the `std::shared_ptr` held by `other`. + //! + //! `Other` is _not_ required to be a polymorphic class: the v-table + //! pointer is already known. + //! + //! @param other A `shared_virtual_ptr` to an object of a class derived from + //! `Class`. + //! + //! @par Requirements + //! @li `std::weak_ptr` must be assignable from + //! `const std::shared_ptr&`. + template< + class Other, + typename = std::enable_if_t&, const std::shared_ptr&>>> + virtual_ptr& operator=( + const virtual_ptr, Registry>& other) { + vp = other.vp; + obj = other.obj; + return *this; + } + + //! Assign from a `weak_virtual_ptr` to a derived class + //! + //! Copy the v-table pointer and the `std::weak_ptr` from `other`. + //! + //! @param other A `weak_virtual_ptr` to an object of a class derived from + //! `Class`. + //! + //! @par Requirements + //! @li `std::weak_ptr` must be assignable from + //! `const std::weak_ptr&`. + template< + class Other, + typename = std::enable_if_t&, const std::weak_ptr&>>> + virtual_ptr& operator=( + const virtual_ptr, Registry>& other) { + vp = other.vp; + obj = other.obj; + return *this; + } + + //! Move-assign from a `weak_virtual_ptr` to a derived class + //! + //! Copy the v-table pointer from `other`, and set it to `nullptr` in + //! `other`. Move the `std::weak_ptr` from `other`. + //! + //! @param other A `weak_virtual_ptr` to an object of a class derived from + //! `Class`. + //! + //! @par Requirements + //! @li `std::weak_ptr` must be assignable from + //! `std::weak_ptr&&`. + template< + class Other, + typename = std::enable_if_t&, std::weak_ptr&&>>> + virtual_ptr& operator=( + virtual_ptr, Registry>&& other) { + vp = std::exchange( + other.vp, detail::box_vptr(detail::null_vptr)); + obj = std::move(other.obj); + return *this; + } + + //! Assign from a `std::shared_ptr` to a derived class + //! + //! Assign the `std::weak_ptr` from `other`. Set the v-table pointer + //! according to the dynamic type of `*other`. + //! + //! @param other A `std::shared_ptr` to a polymorphic object. + //! + //! @par Requirements + //! @li `Other` must be a polymorphic class, according to the `rtti` + //! policy of `Registry`. + //! @li `std::weak_ptr` must be assignable from + //! `const std::shared_ptr&`. + template< + class Other, + typename = std::enable_if_t< + BOOST_OPENMETHOD_UNLESS_MRDOCS(detail::) + IsPolymorphic && + std::is_assignable_v< + std::weak_ptr&, const std::shared_ptr&>>> + virtual_ptr& operator=(const std::shared_ptr& other) { + vp = vptr_of(other); + obj = other; + return *this; + } + + //! Assign from a `std::weak_ptr` to a derived class + //! + //! Assign the `std::weak_ptr` from `other`. Lock `other` to find the + //! dynamic type of the object, and set the v-table pointer accordingly. If + //! `other` has expired, the v-table pointer is set to `nullptr`. + //! + //! @param other A `std::weak_ptr` to a polymorphic object. + //! + //! @par Requirements + //! @li `Other` must be a polymorphic class, according to the `rtti` + //! policy of `Registry`. + //! @li `std::weak_ptr` must be assignable from + //! `const std::weak_ptr&`. + template< + class Other, + typename = std::enable_if_t< + BOOST_OPENMETHOD_UNLESS_MRDOCS(detail::) + IsPolymorphic && + std::is_assignable_v< + std::weak_ptr&, const std::weak_ptr&>>> + virtual_ptr& operator=(const std::weak_ptr& other) { + vp = vptr_of(other.lock()); + obj = other; + return *this; + } + + //! Lock the weak pointer + //! + //! Return a `shared_virtual_ptr` to the object, using the remembered + //! v-table pointer. No hash table lookup is performed. + //! + //! @par Example + //! include:smart_pointers.cpp#classes;weak_lock + //! + //! @return A `shared_virtual_ptr` to the object if it still exists, or an + //! empty `shared_virtual_ptr` with a `nullptr` v-table pointer otherwise. + auto lock() const -> virtual_ptr, Registry> { + if (auto locked = obj.lock()) { + return virtual_ptr, Registry>( + std::move(locked), vp); + } + + return virtual_ptr, Registry>(); + } + + //! Check whether the object still exists + //! + //! @return `true` if the `std::weak_ptr` is empty or the object has been + //! destroyed, `false` otherwise. + auto expired() const noexcept -> bool { + return obj.expired(); + } + + //! Get the number of `std::shared_ptr` objects sharing the object + //! + //! @return The result of `std::weak_ptr::use_count`. + auto use_count() const noexcept -> long { + return obj.use_count(); + } + + //! Release the reference to the object + //! + //! Reset the `std::weak_ptr`. Set the v-table pointer to `nullptr`. + void reset() noexcept { + obj.reset(); + vp = detail::box_vptr(detail::null_vptr); + } + + //! Get the weak pointer to the object + //! + //! @return A const reference to the `std::weak_ptr` + auto pointer() const noexcept -> const std::weak_ptr& { + return obj; + } + + //! Get the v-table pointer + //! + //! @return A pointer to the v-table remembered when the `virtual_ptr` was + //! created or assigned, or `nullptr`. + auto vptr() const { + return detail::unbox_vptr(this->vp); + } +}; + +//! Alias for a `virtual_ptr>`. +//! +//! @par Example +//! include:smart_pointers.cpp#weak_virtual_ptr_alias +//! +//! @see [Smart Pointers](xref:ROOT:smart_pointers.adoc) +template +using weak_virtual_ptr = virtual_ptr, Registry>; + +namespace aliases { +using boost::openmethod::weak_virtual_ptr; +} // namespace aliases + +} // namespace boost::openmethod + +#endif diff --git a/test/compile_fail_weak_ptr_parameter.cpp b/test/compile_fail_weak_ptr_parameter.cpp new file mode 100644 index 00000000..fa645b23 --- /dev/null +++ b/test/compile_fail_weak_ptr_parameter.cpp @@ -0,0 +1,29 @@ +// Copyright (c) 2017-2026 Jean-Louis Leroy +// Distributed under the Boost Software License, Version 1.0. +// See accompanying file LICENSE_1_0.txt +// or copy at http://www.boost.org/LICENSE_1_0.txt) + +// Expected diagnostic, as a CMake regex (see CMakeLists.txt). +// expected-error: a weak pointer cannot be a virtual parameter; call lock\(\) first + +#include +#include + +using namespace boost::openmethod; + +struct Animal { + virtual ~Animal() { + } +}; +struct Cat : Animal {}; + +BOOST_OPENMETHOD(poke, (virtual_>), void); + +BOOST_OPENMETHOD_OVERRIDE(poke, (std::weak_ptr), void) { +} + +int main() { + auto felix = std::make_shared(); + poke(std::weak_ptr(felix)); + return 0; +} diff --git a/test/compile_fail_weak_virtual_ptr_parameter.cpp b/test/compile_fail_weak_virtual_ptr_parameter.cpp new file mode 100644 index 00000000..1a3666c5 --- /dev/null +++ b/test/compile_fail_weak_virtual_ptr_parameter.cpp @@ -0,0 +1,29 @@ +// Copyright (c) 2017-2026 Jean-Louis Leroy +// Distributed under the Boost Software License, Version 1.0. +// See accompanying file LICENSE_1_0.txt +// or copy at http://www.boost.org/LICENSE_1_0.txt) + +// Expected diagnostic, as a CMake regex (see CMakeLists.txt). +// expected-error: a weak pointer cannot be a virtual parameter; call lock\(\) first + +#include +#include + +using namespace boost::openmethod; + +struct Animal { + virtual ~Animal() { + } +}; +struct Cat : Animal {}; + +BOOST_OPENMETHOD(poke, (weak_virtual_ptr), void); + +BOOST_OPENMETHOD_OVERRIDE(poke, (weak_virtual_ptr), void) { +} + +int main() { + auto felix = std::make_shared(); + poke(weak_virtual_ptr(felix)); + return 0; +} diff --git a/test/test_weak_virtual_ptr.cpp b/test/test_weak_virtual_ptr.cpp new file mode 100644 index 00000000..850afc0e --- /dev/null +++ b/test/test_weak_virtual_ptr.cpp @@ -0,0 +1,313 @@ +// Copyright (c) 2017-2026 Jean-Louis Leroy +// Distributed under the Boost Software License, Version 1.0. +// See accompanying file LICENSE_1_0.txt +// or copy at http://www.boost.org/LICENSE_1_0.txt) + +#include + +#define BOOST_TEST_MODULE weak_virtual_ptr +#include + +#include "test_virtual_ptr_value_semantics.hpp" + +#include +#include +#include + +// A weak virtual_ptr is neither a smart virtual_ptr in the `IsSmartPtr` sense +// (no `virtual_traits`, no `rebind`) nor a plain one. +static_assert(!IsSmartPtr, default_registry>); + +static_assert(std::is_same_v::element_type, Animal>); +static_assert(std::is_same_v< + decltype(std::declval>().lock()), + shared_virtual_ptr>); +static_assert(std::is_same_v< + decltype(std::declval>().pointer()), + const std::weak_ptr&>); + +// Construction is allowed from shared and weak pointers, virtual or not... +static_assert(std::is_constructible_v< + weak_virtual_ptr, shared_virtual_ptr>); +static_assert( + std::is_constructible_v, shared_virtual_ptr>); +static_assert( + std::is_constructible_v, weak_virtual_ptr>); +static_assert( + std::is_constructible_v, std::shared_ptr>); +static_assert( + std::is_constructible_v, std::weak_ptr>); +static_assert(std::is_constructible_v< + weak_virtual_ptr, shared_virtual_ptr>); + +// ...but not from a plain pointer, reference or virtual_ptr, nor from a +// different class or a const object... +static_assert(!std::is_constructible_v, Animal&>); +static_assert(!std::is_constructible_v, Animal*>); +static_assert( + !std::is_constructible_v, virtual_ptr>); +static_assert( + !std::is_constructible_v, shared_virtual_ptr>); +static_assert(!std::is_constructible_v< + weak_virtual_ptr, shared_virtual_ptr>); +static_assert( + !std::is_constructible_v< + weak_virtual_ptr, std::shared_ptr>); +static_assert(!std::is_constructible_v< + weak_virtual_ptr, std::weak_ptr>); + +// ...and a weak virtual_ptr converts to nothing but another weak virtual_ptr. +static_assert( + !std::is_constructible_v, weak_virtual_ptr>); +static_assert( + !std::is_assignable_v&, weak_virtual_ptr>); +static_assert(!std::is_constructible_v< + shared_virtual_ptr, weak_virtual_ptr>); +static_assert(!std::is_assignable_v< + shared_virtual_ptr&, weak_virtual_ptr>); +static_assert(!std::is_constructible_v< + std::shared_ptr, weak_virtual_ptr>); + +BOOST_AUTO_TEST_CASE_TEMPLATE( + weak_virtual_ptr_from_shared_virtual_ptr, Registry, test_policies) { + init_test(); + + auto snoopy = std::make_shared(); + shared_virtual_ptr shared(snoopy); + + weak_virtual_ptr weak(shared); + BOOST_TEST(!weak.expired()); + BOOST_TEST(weak.use_count() == 2); + BOOST_TEST(weak.vptr() == Registry::template static_vptr); + BOOST_TEST(weak.pointer().lock() == snoopy); + + { + auto locked = weak.lock(); + static_assert(std::is_same_v< + decltype(locked), shared_virtual_ptr>); + BOOST_TEST(locked.get() == snoopy.get()); + BOOST_TEST(locked.vptr() == Registry::template static_vptr); + BOOST_TEST(weak.use_count() == 3); + } + + BOOST_TEST(weak.use_count() == 2); + + shared = nullptr; + snoopy.reset(); + BOOST_TEST(weak.expired()); + BOOST_TEST(weak.use_count() == 0); + + auto locked = weak.lock(); + BOOST_TEST(locked.get() == nullptr); + BOOST_TEST(locked.vptr() == nullptr); + + weak.reset(); + BOOST_TEST(weak.expired()); + BOOST_TEST(weak.vptr() == nullptr); +} + +BOOST_AUTO_TEST_CASE_TEMPLATE( + weak_virtual_ptr_from_std_pointers, Registry, test_policies) { + init_test(); + + auto felix = std::make_shared(); + + { + weak_virtual_ptr weak(felix); + BOOST_TEST(weak.vptr() == Registry::template static_vptr); + BOOST_TEST(weak.lock().get() == felix.get()); + } + + { + std::weak_ptr std_weak = felix; + weak_virtual_ptr weak(std_weak); + BOOST_TEST(weak.vptr() == Registry::template static_vptr); + BOOST_TEST(weak.lock().get() == felix.get()); + } + + { + // an expired std::weak_ptr yields an expired weak virtual_ptr + std::weak_ptr std_weak; + { + auto dead = std::make_shared(); + std_weak = dead; + } + + weak_virtual_ptr weak(std_weak); + BOOST_TEST(weak.expired()); + BOOST_TEST(weak.vptr() == nullptr); + BOOST_TEST(weak.lock().get() == nullptr); + } + + { + // an empty std::shared_ptr yields an empty weak virtual_ptr + weak_virtual_ptr weak{std::shared_ptr()}; + BOOST_TEST(weak.expired()); + BOOST_TEST(weak.vptr() == nullptr); + } +} + +BOOST_AUTO_TEST_CASE_TEMPLATE( + weak_virtual_ptr_default_and_nullptr, Registry, test_policies) { + init_test(); + + { + weak_virtual_ptr weak; + BOOST_TEST(weak.expired()); + BOOST_TEST(weak.use_count() == 0); + BOOST_TEST(weak.vptr() == nullptr); + BOOST_TEST(weak.lock().get() == nullptr); + } + + { + weak_virtual_ptr weak(nullptr); + BOOST_TEST(weak.expired()); + BOOST_TEST(weak.vptr() == nullptr); + } + + { + auto snoopy = std::make_shared(); + weak_virtual_ptr weak(snoopy); + BOOST_TEST(!weak.expired()); + + weak = nullptr; + BOOST_TEST(weak.expired()); + BOOST_TEST(weak.vptr() == nullptr); + } +} + +BOOST_AUTO_TEST_CASE_TEMPLATE( + weak_virtual_ptr_assign, Registry, test_policies) { + init_test(); + + auto snoopy = std::make_shared(); + auto felix = std::make_shared(); + weak_virtual_ptr weak; + + weak = shared_virtual_ptr(snoopy); + BOOST_TEST(weak.lock().get() == snoopy.get()); + BOOST_TEST(weak.vptr() == Registry::template static_vptr); + + weak = shared_virtual_ptr(felix); + BOOST_TEST(weak.lock().get() == felix.get()); + BOOST_TEST(weak.vptr() == Registry::template static_vptr); + + weak = snoopy; + BOOST_TEST(weak.lock().get() == snoopy.get()); + BOOST_TEST(weak.vptr() == Registry::template static_vptr); + + weak = std::weak_ptr(felix); + BOOST_TEST(weak.lock().get() == felix.get()); + BOOST_TEST(weak.vptr() == Registry::template static_vptr); + + weak_virtual_ptr weak_dog(snoopy); + weak = weak_dog; + BOOST_TEST(weak.lock().get() == snoopy.get()); + BOOST_TEST(weak.vptr() == Registry::template static_vptr); + BOOST_TEST(!weak_dog.expired()); + + weak = *&weak; // self-assignment + BOOST_TEST(weak.lock().get() == snoopy.get()); + BOOST_TEST(weak.vptr() == Registry::template static_vptr); +} + +BOOST_AUTO_TEST_CASE_TEMPLATE( + weak_virtual_ptr_copy_move, Registry, test_policies) { + init_test(); + + auto snoopy = std::make_shared(); + weak_virtual_ptr weak_dog(snoopy); + + { + weak_virtual_ptr copy(weak_dog); + BOOST_TEST(copy.lock().get() == snoopy.get()); + BOOST_TEST(copy.vptr() == Registry::template static_vptr); + BOOST_TEST(weak_dog.lock().get() == snoopy.get()); + } + + { + // upcast, copying + weak_virtual_ptr base(weak_dog); + BOOST_TEST(base.lock().get() == snoopy.get()); + BOOST_TEST(base.vptr() == Registry::template static_vptr); + BOOST_TEST(weak_dog.lock().get() == snoopy.get()); + } + + { + weak_virtual_ptr source(snoopy); + weak_virtual_ptr moved(std::move(source)); + BOOST_TEST(moved.lock().get() == snoopy.get()); + BOOST_TEST(moved.vptr() == Registry::template static_vptr); + BOOST_TEST(source.expired()); + BOOST_TEST(source.vptr() == nullptr); + } + + { + // upcast, moving + weak_virtual_ptr source(snoopy); + weak_virtual_ptr moved(std::move(source)); + BOOST_TEST(moved.lock().get() == snoopy.get()); + BOOST_TEST(moved.vptr() == Registry::template static_vptr); + BOOST_TEST(source.expired()); + BOOST_TEST(source.vptr() == nullptr); + } + + { + weak_virtual_ptr source(snoopy); + weak_virtual_ptr moved; + moved = std::move(source); + BOOST_TEST(moved.lock().get() == snoopy.get()); + BOOST_TEST(moved.vptr() == Registry::template static_vptr); + BOOST_TEST(source.expired()); + BOOST_TEST(source.vptr() == nullptr); + } +} + +BOOST_AUTO_TEST_CASE_TEMPLATE( + weak_virtual_ptr_non_polymorphic, Registry, test_policies) { + // The v-table pointer is copied from the shared_virtual_ptr, so the class + // need not be polymorphic; only the vptr lookup would require that. + BOOST_OPENMETHOD_REGISTER(use_classes); + init_test(); + + auto shared = make_shared_virtual(); + weak_virtual_ptr weak(shared); + BOOST_TEST(weak.vptr() == Registry::template static_vptr); + BOOST_TEST(weak.lock().get() == shared.get()); + BOOST_TEST( + weak.lock().vptr() == Registry::template static_vptr); +} + +struct BOOST_OPENMETHOD_ID(poke); + +BOOST_AUTO_TEST_CASE_TEMPLATE( + weak_virtual_ptr_dispatch, Registry, test_policies) { + using poke = method< + BOOST_OPENMETHOD_ID(poke), + auto(shared_virtual_ptr)->std::string, Registry>; + + struct overriders { + static auto poke_dog(shared_virtual_ptr) -> std::string { + return "bark"; + } + + static auto poke_cat(shared_virtual_ptr) -> std::string { + return "hiss"; + } + }; + + BOOST_OPENMETHOD_REGISTER( + typename poke::template override< + overriders::poke_dog, overriders::poke_cat>); + + init_test(); + + auto snoopy = std::make_shared(); + auto felix = std::make_shared(); + weak_virtual_ptr weak_dog(snoopy); + weak_virtual_ptr weak_cat(felix); + + // lock, then dispatch + BOOST_TEST(poke::fn(weak_dog.lock()) == "bark"); + BOOST_TEST(poke::fn(weak_cat.lock()) == "hiss"); +} From 6fbf2d591f5dd06f6efcac04f5e5181f2d1cdcd3 Mon Sep 17 00:00:00 2001 From: Jean-Louis Leroy Date: Sat, 5 Sep 2026 16:21:55 -0400 Subject: [PATCH 2/3] test: use namespace-scope overriders in the weak_virtual_ptr dispatch test The dispatch test registered the static member functions of a local class as override<> template arguments. That is valid C++17, but gcc before 13 rejects it ("has no linkage"), failing the gcc-10/11/12 and Cygwin 32-bit jobs. Use namespace-scope function templates instead, as test_util.hpp's poke_bear does. Co-Authored-By: Claude Opus 5 --- test/test_weak_virtual_ptr.cpp | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/test/test_weak_virtual_ptr.cpp b/test/test_weak_virtual_ptr.cpp index 850afc0e..9feb1873 100644 --- a/test/test_weak_virtual_ptr.cpp +++ b/test/test_weak_virtual_ptr.cpp @@ -280,25 +280,27 @@ BOOST_AUTO_TEST_CASE_TEMPLATE( struct BOOST_OPENMETHOD_ID(poke); +// Namespace-scope templates: gcc before 13 does not accept the static member +// functions of a local class as template arguments (no linkage). +template +auto poke_dog(shared_virtual_ptr) -> std::string { + return "bark"; +} + +template +auto poke_cat(shared_virtual_ptr) -> std::string { + return "hiss"; +} + BOOST_AUTO_TEST_CASE_TEMPLATE( weak_virtual_ptr_dispatch, Registry, test_policies) { using poke = method< BOOST_OPENMETHOD_ID(poke), auto(shared_virtual_ptr)->std::string, Registry>; - struct overriders { - static auto poke_dog(shared_virtual_ptr) -> std::string { - return "bark"; - } - - static auto poke_cat(shared_virtual_ptr) -> std::string { - return "hiss"; - } - }; - BOOST_OPENMETHOD_REGISTER( typename poke::template override< - overriders::poke_dog, overriders::poke_cat>); + poke_dog, poke_cat>); init_test(); From e3cefacfbd9540cdc022da8d90748f0669c66090 Mon Sep 17 00:00:00 2001 From: Jean-Louis Leroy Date: Tue, 15 Sep 2026 08:15:24 -0400 Subject: [PATCH 3/3] interop: make weak_virtual_ptr a class of its own As a partial specialization of virtual_ptr, weak_virtual_ptr matched everything written for virtual_ptr: is_virtual, so it could not be an ordinary non-virtual method parameter; the converting constructors of the plain virtual_ptr, hence the has_get gate in core.hpp; the free operator==, which then failed inside core.hpp; final_virtual_ptr; and a user virtual_traits made it ambiguous. A weak pointer is not a virtual_ptr - it cannot be dereferenced or dispatched on - so model it as a class of its own, with the same members. What it needs from a shared virtual_ptr has no public route: the boxed v-table pointer, which under indirect_vptr is the address of the cell that initialize() rewrites and which vptr() unboxes away, and the constructor that takes a v-table pointer, so that lock() skips the lookup. core.hpp therefore gains detail::virtual_ptr_access, a generic door to those two things, befriended by both virtual_ptr specializations; weak_virtual_ptr uses it to copy the pointer from a shared virtual_ptr and hand it back in lock(). core names no client class: the next adaptor - boost::weak_ptr - uses the same door. Fold in the review's findings on the way. Move constructors and assignments are noexcept, so containers relocate by moving. Construction from a std::weak_ptr to another class locks it once, not twice, and an expired source is copied as is, so that it keeps its control block as far as the standard library allows - libstdc++ shares ownership with a source that is expired but not empty, as [util.smartptr.weak.const] requires; libc++ locks first, so there an expired source of a different class yields an empty weak pointer. owner_before and swap support the owner-keyed containers a cache of weak pointers is built on - with a comparator of one's own, since std::owner_less is generic only in libstdc++; MSVC's and libc++'s accept std::shared_ptr and std::weak_ptr alone. The "call lock() first" diagnostic now fires for every form of a weak virtual parameter - value, &, const& and && - and for virtual_>; a bare weak_virtual_ptr parameter is valid. A virtual_ptr>, which is what final_virtual_ptr(std::weak_ptr) would build, is rejected with "use weak_virtual_ptr", unless virtual_traits is specialized for std::weak_ptr. The doc comment says what a remembered v-table pointer is safe against, and what it is not: a second initialize(), unless the registry uses indirect_vptr. Like the smart pointer aliases, weak_virtual_ptr defaults its registry to the affinity its class declares, so that it agrees with the shared_virtual_ptr it converts to and from. The compile-fail markers no longer contain a `;`: PASS_REGULAR_EXPRESSION is a CMake list, so it split each regex into two alternatives, and a test passed on either half. The CMake loop now refuses such a marker. Docs: the Weak Pointers section no longer swallows the unique_ptr AST example, describes a class rather than an alias, and ref_headers.adoc no longer lists the header under "use in virtual parameters". Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_018FzFtVWNk6d4Wut5SboCZC --- CLAUDE.md | 4 +- doc/modules/ROOT/pages/ref_headers.adoc | 12 +- doc/modules/ROOT/pages/smart_pointers.adoc | 48 +-- doc/modules/ROOT/snippets/smart_pointers.cpp | 4 +- include/boost/openmethod/core.hpp | 47 ++- .../boost/openmethod/interop/std_weak_ptr.hpp | 306 ++++++++++++------ test/CMakeLists.txt | 9 +- ...ompile_fail_final_virtual_ptr_weak_ptr.cpp | 26 ++ test/compile_fail_weak_ptr_parameter.cpp | 3 +- ...ompile_fail_weak_virtual_ptr_parameter.cpp | 5 +- test/test_adl_registry_smart_ptr.cpp | 11 + test/test_weak_virtual_ptr.cpp | 130 +++++++- 12 files changed, 451 insertions(+), 154 deletions(-) create mode 100644 test/compile_fail_final_virtual_ptr_weak_ptr.cpp diff --git a/CLAUDE.md b/CLAUDE.md index 6013f039..73aa6b18 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -92,7 +92,9 @@ the license header: `MATCHES "//[ \t]*expected-error:[ \t]*([^\r\n]+)"`, and hands it to `openmethod_compile_fail_test` as the test's `PASS_REGULAR_EXPRESSION`. Adding a test is dropping in a file - no build-file edit. A file with no marker is a configure-time `FATAL_ERROR`, so a -silently unchecked test cannot slip through. The glob has no `CONFIGURE_DEPENDS` (matching the +silently unchecked test cannot slip through. So is a marker containing a `;`: +`PASS_REGULAR_EXPRESSION` is a CMake list, so the `;` would split the regex into two alternatives +and the test would pass on either half. Write `.*` in its place. The glob has no `CONFIGURE_DEPENDS` (matching the `test_*.cpp` glob above it), so a new file needs a manual re-run of `cmake`. Where the expected wording differs across compilers, match the common substring and say why in a diff --git a/doc/modules/ROOT/pages/ref_headers.adoc b/doc/modules/ROOT/pages/ref_headers.adoc index 0cde474d..0d47532b 100644 --- a/doc/modules/ROOT/pages/ref_headers.adoc +++ b/doc/modules/ROOT/pages/ref_headers.adoc @@ -20,8 +20,8 @@ convenient macros. * xref:#initialize[``] to initialize the library. Typically only included in the translation unit containing `main`. -The following headers make it possible to use standard smart pointers in virtual -parameters: +The following headers make it possible to use standard smart pointers with +`virtual_ptr`: * xref:#std_shared_ptr[``] to use `std::shared_ptr` in virtual parameters. @@ -78,10 +78,10 @@ Provides a `virtual_traits` specialization that makes it possible to use a [#std_weak_ptr] ### link:{headers-url}/boost/openmethod/interop/std_weak_ptr.hpp[] -Provides cpp:weak_virtual_ptr[], a `virtual_ptr` that tracks an object with a -`std::weak_ptr` and remembers its v-table pointer. It cannot be used in virtual -parameters; its `lock` function returns a cpp:shared_virtual_ptr[], without a -hash table lookup. +Provides cpp:weak_virtual_ptr[], a class that tracks an object with a +`std::weak_ptr` and remembers its v-table pointer. It is not a `virtual_ptr`, +and cannot be used in virtual parameters; its `lock` function returns a +cpp:shared_virtual_ptr[], without a hash table lookup. [#boost_intrusive_ptr] ### link:{headers-url}/boost/openmethod/interop/boost_intrusive_ptr.hpp[] diff --git a/doc/modules/ROOT/pages/smart_pointers.adoc b/doc/modules/ROOT/pages/smart_pointers.adoc index 3231ae16..cfd17665 100644 --- a/doc/modules/ROOT/pages/smart_pointers.adoc +++ b/doc/modules/ROOT/pages/smart_pointers.adoc @@ -67,6 +67,14 @@ in ``, and support for ``. `` does not include smart pointer headers, so they must be included explicitly. +Here is a variation of the AST example that uses dynamic allocation and unique +pointers: + +[source,c++] +---- +include::example$ast_unique_ptr.cpp[tag=content] +---- + [#weak_pointers] ## Weak Pointers @@ -77,15 +85,16 @@ object that is tracked by weak pointers - in a cache, an observer list, or a back pointer - is typically an object that methods will be called on, once a weak pointer has been locked. -- cpp:weak_virtual_ptr[] is an alias for `virtual_ptr>` +- cpp:weak_virtual_ptr[] tracks an object with a `std::weak_ptr`, and + remembers its v-table pointer -A `weak_virtual_ptr` is a storage facility. It is constructed from a -`shared_virtual_ptr` (or from a `std::shared_ptr` or a `std::weak_ptr`), and -it remembers the v-table pointer along with the weak pointer. It cannot be -dereferenced. Its `lock` function returns a `shared_virtual_ptr`, which can be -passed to methods. Since the v-table pointer is copied, not looked up, `lock` -costs no more than `std::weak_ptr::lock`. It returns an empty -`shared_virtual_ptr` if the object no longer exists. +A `weak_virtual_ptr` is a storage facility, not a `virtual_ptr`. It is +constructed from a `shared_virtual_ptr` (or from a `std::shared_ptr` or a +`std::weak_ptr`), and it remembers the v-table pointer along with the weak +pointer. It cannot be dereferenced. Its `lock` function returns a +`shared_virtual_ptr`, which can be passed to methods. Since the v-table pointer +is copied, not looked up, `lock` costs no more than `std::weak_ptr::lock`. It +returns an empty `shared_virtual_ptr` if the object no longer exists. [source,c++] ---- @@ -98,20 +107,17 @@ animal = nullptr; std::cout << std::boolalpha << observer.expired() << "\n"; // true ---- -Remembering the v-table pointer is safe because a `std::weak_ptr` keeps the -control block alive: once the object has been destroyed, the weak pointer stays -expired, and the v-table pointer can never be applied to another object. +Remembering the v-table pointer is safe with respect to the lifetime of the +object, because a `std::weak_ptr` keeps the control block alive: once the +object has been destroyed, the weak pointer stays expired, and the v-table +pointer can never be applied to another object. As for any `virtual_ptr`, the +v-table pointer is invalidated if `initialize` is called again, unless the +registry uses the cpp:indirect_vptr[] policy. A `weak_virtual_ptr` converts to a `weak_virtual_ptr` to a base class, but not to a plain or a shared `virtual_ptr`. A cast to a derived class requires the -object: use `lock`, then `cast`. Support for `std::weak_ptr` is provided in -``, which also includes the +object: use `lock`, then `cast`. Since it is not a `virtual_ptr`, a +`weak_virtual_ptr` cannot be used in a virtual parameter, but it can be passed +to a method as an ordinary parameter. Support for `std::weak_ptr` is provided +in ``, which also includes the `std::shared_ptr` header. - -Here is a variation of the AST example that uses dynamic allocation and unique -pointers: - -[source,c++] ----- -include::example$ast_unique_ptr.cpp[tag=content] ----- diff --git a/doc/modules/ROOT/snippets/smart_pointers.cpp b/doc/modules/ROOT/snippets/smart_pointers.cpp index c8b11f70..26291743 100644 --- a/doc/modules/ROOT/snippets/smart_pointers.cpp +++ b/doc/modules/ROOT/snippets/smart_pointers.cpp @@ -217,13 +217,13 @@ BOOST_AUTO_TEST_CASE(weak_ptr_examples) { } { - // tag::weak_virtual_ptr_alias[] + // tag::weak_pointer[] shared_virtual_ptr animal = make_shared_virtual(); weak_virtual_ptr observer = animal; std::weak_ptr weak = observer.pointer(); BOOST_TEST(animal.pointer().use_count() == 1); BOOST_TEST(weak.lock() == animal.pointer()); - // end::weak_virtual_ptr_alias[] + // end::weak_pointer[] } } diff --git a/include/boost/openmethod/core.hpp b/include/boost/openmethod/core.hpp index c2417e7a..4a2ccc1b 100644 --- a/include/boost/openmethod/core.hpp +++ b/include/boost/openmethod/core.hpp @@ -140,6 +140,9 @@ using macro_default_registry = BOOST_OPENMETHOD_DEFAULT_REGISTRY; template constexpr bool false_t = false; // workaround before CWG2518/P2593R1 +template +struct virtual_ptr_access; + } // namespace detail namespace detail { @@ -828,13 +831,6 @@ struct same_smart_ptr_aux< typename virtual_traits::template rebind< typename Other::element_type>> {}; -template -constexpr bool has_get = false; - -template -constexpr bool - has_get().get())>> = true; - } // namespace detail BOOST_OPENMETHOD_OPEN_NAMESPACE_DETAIL_UNLESS_MRDOCS @@ -965,6 +961,26 @@ inline auto unbox_vptr(const vptr_type* vpp) { inline vptr_type null_vptr = nullptr; +// Access to the parts of a `virtual_ptr`, for the classes that carry a +// v-table pointer of their own and exchange it with one: copy it from a +// `virtual_ptr`, hand it back later. The pointer is the boxed one - under +// `indirect_vptr`, the address of the cell that `initialize()` rewrites, which +// the public `vptr()` unboxes away - and constructing with a given v-table +// pointer skips the lookup, which no public constructor does. +template +struct virtual_ptr_access { + using boxed_vptr_type = decltype(VirtualPtr::vp); + + static auto boxed_vptr(const VirtualPtr& ptr) -> boxed_vptr_type { + return ptr.vp; + } + + template + static auto make(Arg&& obj, boxed_vptr_type vp) -> VirtualPtr { + return VirtualPtr(std::forward(obj), vp); + } +}; + } // namespace detail //! Create a `virtual_ptr` for an object of a known exact class. @@ -1111,6 +1127,8 @@ class virtual_ptr { #ifndef __MRDOCS__ template friend class virtual_ptr; + template + friend struct detail::virtual_ptr_access; template friend auto final_virtual_ptr(Arg&& obj); #endif @@ -1256,10 +1274,8 @@ class virtual_ptr { //! @li @c Other's object pointer must be assignable to a @c Class*. template< class Other, - typename = std::enable_if_t< - std::is_constructible_v< - Class*, typename virtual_ptr::element_type*> && - detail::has_get>>> + typename = std::enable_if_t::element_type*>>> virtual_ptr(const virtual_ptr& other) : vp(other.vp), obj(other.get()) { } @@ -1366,11 +1382,8 @@ class virtual_ptr { //! @li @c Other's object pointer must be assignable to a @c Class*. template< class Other, - typename = std::enable_if_t< - std::is_assignable_v< - Class*&, - typename virtual_ptr::element_type*> && - detail::has_get>>> + typename = std::enable_if_t::element_type*>>> virtual_ptr& operator=(const virtual_ptr& other) { obj = other.get(); vp = other.vp; @@ -1474,6 +1487,8 @@ class virtual_ptr< #ifndef __MRDOCS__ template friend class virtual_ptr; + template + friend struct detail::virtual_ptr_access; template friend auto final_virtual_ptr(Arg&& obj); #endif diff --git a/include/boost/openmethod/interop/std_weak_ptr.hpp b/include/boost/openmethod/interop/std_weak_ptr.hpp index a554aed5..c3b456df 100644 --- a/include/boost/openmethod/interop/std_weak_ptr.hpp +++ b/include/boost/openmethod/interop/std_weak_ptr.hpp @@ -8,83 +8,102 @@ #include #include +#include namespace boost::openmethod { + +template> +class weak_virtual_ptr; + namespace detail { // A weak pointer may refer to an object that no longer exists, so there is // nothing to dispatch on. `virtual_traits` is deliberately *not* specialized -// for `std::weak_ptr`: that keeps `IsSmartPtr` false, which is what lets the -// hand-written `virtual_ptr` specialization below win over the generic smart -// pointer one. These specializations only replace the vague diagnostics that -// would result from using a weak pointer as a virtual parameter with a useful -// one. - -template -struct validate_method_parameter>, Registry, void> : - std::false_type { +// for `std::weak_ptr`, and `weak_virtual_ptr` is not a `virtual_ptr`. The +// specializations below only replace the vague diagnostics that would result +// from using either as a virtual parameter with a useful one, in the four +// forms a virtual parameter can take. A `weak_virtual_ptr` that is not wrapped +// in `virtual_` is an ordinary parameter, and needs no specialization. + +template +struct reject_weak_parameter : std::false_type { static_assert( false_t, "a weak pointer cannot be a virtual parameter; call lock() first"); }; template -struct validate_method_parameter< - virtual_ptr, Registry>, Registry, void> : std::false_type { - static_assert( - false_t, - "a weak pointer cannot be a virtual parameter; call lock() first"); -}; +struct validate_method_parameter>, Registry, void> : + reject_weak_parameter {}; + +template +struct validate_method_parameter&>, Registry, void> : + reject_weak_parameter {}; template struct validate_method_parameter< - virtual_ptr, Registry>&, Registry, void> : - std::false_type { - static_assert( - false_t, - "a weak pointer cannot be a virtual parameter; call lock() first"); -}; + virtual_&>, Registry, void> : + reject_weak_parameter {}; template +struct validate_method_parameter&&>, Registry, void> : + reject_weak_parameter {}; + +template struct validate_method_parameter< - const virtual_ptr, Registry>&, Registry, void> : - std::false_type { - static_assert( - false_t, - "a weak pointer cannot be a virtual parameter; call lock() first"); -}; + virtual_>, MethodRegistry, void> : + reject_weak_parameter {}; + +template +struct validate_method_parameter< + virtual_&>, MethodRegistry, void> : + reject_weak_parameter {}; + +template +struct validate_method_parameter< + virtual_&>, MethodRegistry, void> : + reject_weak_parameter {}; + +template +struct validate_method_parameter< + virtual_&&>, MethodRegistry, void> : + reject_weak_parameter {}; } // namespace detail -//! Wide pointer combining a `std::weak_ptr` to an object and a pointer to its -//! v-table +//! Weak pointer to an object, remembering its v-table pointer +//! +//! A `weak_virtual_ptr` tracks an object with a `std::weak_ptr`, and +//! remembers its v-table pointer. It is a storage facility, not a +//! `virtual_ptr`: it cannot be dereferenced, compared, or used as a virtual +//! parameter, because the object may no longer exist. It can be passed to a +//! method as an ordinary parameter. Call `lock()` to obtain a +//! @ref shared_virtual_ptr, then use it as usual. Since the v-table pointer is +//! copied from the weak pointer, `lock()` costs no more than +//! `std::weak_ptr::lock()`: no hash table lookup is needed. //! -//! This specialization of `virtual_ptr` tracks the object with a -//! `std::weak_ptr`, and remembers its v-table pointer. It is a storage -//! facility: unlike other kinds of `virtual_ptr`, it cannot be dereferenced, -//! and it cannot be used as a virtual parameter, because the object may no -//! longer exist. Instead, call `lock()` to obtain a @ref shared_virtual_ptr, -//! then use it as usual. Since the v-table pointer is copied from the weak -//! pointer, `lock()` costs no more than `std::weak_ptr::lock()`: no hash table -//! lookup is needed. +//! Remembering the v-table pointer is safe with respect to the lifetime of the +//! object: a `std::weak_ptr` keeps the control block alive, so once the object +//! is destroyed, the weak pointer stays expired, and the v-table pointer can +//! never be applied to another object. //! -//! Remembering the v-table pointer is safe: a `std::weak_ptr` keeps the -//! control block alive, so once the object is destroyed, the weak pointer -//! stays expired. The v-table pointer can never be applied to another object. +//! @note As for any `virtual_ptr`, the remembered v-table pointer is +//! invalidated when @ref boost::openmethod::initialize is called again, unless +//! the registry uses @ref policies::indirect_vptr. //! //! @par Example //! include:smart_pointers.cpp#classes;weak_lock //! //! @tparam Class The class of the object, possibly cv-qualified -//! @tparam Registry The registry in which `Class` is registered +//! @tparam Registry The registry in which `Class` is registered. Defaults to +//! the registry `Class` has an affinity for, see @ref registry_affinity. //! -//! @see @ref weak_virtual_ptr //! @see [Smart Pointers](xref:ROOT:smart_pointers.adoc) template -class virtual_ptr, Registry, void> { +class weak_virtual_ptr { #ifndef __MRDOCS__ - template - friend class virtual_ptr; + template + friend class weak_virtual_ptr; #endif static constexpr bool use_indirect_vptrs = Registry::has_indirect_vptr; @@ -98,6 +117,34 @@ class virtual_ptr, Registry, void> { other ? detail::acquire_vptr(*other) : detail::null_vptr); } + template + static auto vptr_of( + const virtual_ptr, Registry>& other) { + return detail::virtual_ptr_access< + virtual_ptr, Registry>>::boxed_vptr(other); + } + + // Lock `other` once: it is needed to find the dynamic type of the object, + // and the `std::weak_ptr` is then constructed from the `std::shared_ptr`, + // which does not lock again, as construction from a `std::weak_ptr` to a + // different class would. An expired `other` is copied as is, which keeps + // its control block - and with it `expired()`, `use_count()` and owner + // identity - as far as the standard library allows: libstdc++ shares + // ownership with a source that is expired but not empty, as + // [util.smartptr.weak.const] requires; libc++ locks first, so an expired + // source of a *different* class yields an empty weak pointer there. + template + void assign(const std::weak_ptr& other) { + auto locked = other.lock(); + vp = vptr_of(locked); + + if (locked) { + obj = locked; + } else { + obj = other; + } + } + public: //! Class pointed to by the `std::weak_ptr` using element_type = Class; @@ -106,7 +153,7 @@ class virtual_ptr, Registry, void> { //! //! Construct an empty `std::weak_ptr`. Set the v-table pointer to //! `nullptr`. - virtual_ptr() : + weak_virtual_ptr() : vp(detail::box_vptr(detail::null_vptr)) { } @@ -116,13 +163,13 @@ class virtual_ptr, Registry, void> { //! `nullptr`. //! //! @param value A `nullptr`. - explicit virtual_ptr(std::nullptr_t) : + explicit weak_virtual_ptr(std::nullptr_t) : vp(detail::box_vptr(detail::null_vptr)) { } - virtual_ptr(const virtual_ptr& other) = default; + weak_virtual_ptr(const weak_virtual_ptr& other) = default; - virtual_ptr(virtual_ptr&& other) : + weak_virtual_ptr(weak_virtual_ptr&& other) noexcept : vp(std::exchange( other.vp, detail::box_vptr(detail::null_vptr))), obj(std::move(other.obj)) { @@ -149,8 +196,9 @@ class virtual_ptr, Registry, void> { class Other, typename = std::enable_if_t, const std::shared_ptr&>>> - virtual_ptr(const virtual_ptr, Registry>& other) : - vp(other.vp), obj(other.obj) { + weak_virtual_ptr( + const virtual_ptr, Registry>& other) : + vp(vptr_of(other)), obj(other.pointer()) { } //! Construct from a `weak_virtual_ptr` to a derived class @@ -167,7 +215,7 @@ class virtual_ptr, Registry, void> { class Other, typename = std::enable_if_t, const std::weak_ptr&>>> - virtual_ptr(const virtual_ptr, Registry>& other) : + weak_virtual_ptr(const weak_virtual_ptr& other) : vp(other.vp), obj(other.obj) { } @@ -186,7 +234,7 @@ class virtual_ptr, Registry, void> { class Other, typename = std::enable_if_t, std::weak_ptr&&>>> - virtual_ptr(virtual_ptr, Registry>&& other) : + weak_virtual_ptr(weak_virtual_ptr&& other) noexcept : vp(std::exchange( other.vp, detail::box_vptr(detail::null_vptr))), obj(std::move(other.obj)) { @@ -206,12 +254,11 @@ class virtual_ptr, Registry, void> { //! `const std::shared_ptr&`. template< class Other, - typename = std::enable_if_t< - BOOST_OPENMETHOD_UNLESS_MRDOCS(detail::) - IsPolymorphic && - std::is_constructible_v< - std::weak_ptr, const std::shared_ptr&>>> - virtual_ptr(const std::shared_ptr& other) : + typename = std::enable_if_t>, + typename = std::enable_if_t, const std::shared_ptr&>>> + weak_virtual_ptr(const std::shared_ptr& other) : vp(vptr_of(other)), obj(other) { } @@ -230,13 +277,12 @@ class virtual_ptr, Registry, void> { //! `const std::weak_ptr&`. template< class Other, - typename = std::enable_if_t< - BOOST_OPENMETHOD_UNLESS_MRDOCS(detail::) - IsPolymorphic && - std::is_constructible_v< - std::weak_ptr, const std::weak_ptr&>>> - virtual_ptr(const std::weak_ptr& other) : - vp(vptr_of(other.lock())), obj(other) { + typename = std::enable_if_t>, + typename = std::enable_if_t, const std::weak_ptr&>>> + weak_virtual_ptr(const std::weak_ptr& other) { + assign(other); } //! Assign from `nullptr` @@ -244,15 +290,14 @@ class virtual_ptr, Registry, void> { //! Reset the `std::weak_ptr`. Set the v-table pointer to `nullptr`. //! //! @param value A `nullptr`. - virtual_ptr& operator=(std::nullptr_t) { - obj.reset(); - vp = detail::box_vptr(detail::null_vptr); + weak_virtual_ptr& operator=(std::nullptr_t) noexcept { + reset(); return *this; } - virtual_ptr& operator=(const virtual_ptr& other) = default; + weak_virtual_ptr& operator=(const weak_virtual_ptr& other) = default; - virtual_ptr& operator=(virtual_ptr&& other) { + weak_virtual_ptr& operator=(weak_virtual_ptr&& other) noexcept { vp = std::exchange( other.vp, detail::box_vptr(detail::null_vptr)); obj = std::move(other.obj); @@ -277,10 +322,10 @@ class virtual_ptr, Registry, void> { class Other, typename = std::enable_if_t&, const std::shared_ptr&>>> - virtual_ptr& operator=( + weak_virtual_ptr& operator=( const virtual_ptr, Registry>& other) { - vp = other.vp; - obj = other.obj; + vp = vptr_of(other); + obj = other.pointer(); return *this; } @@ -298,8 +343,8 @@ class virtual_ptr, Registry, void> { class Other, typename = std::enable_if_t&, const std::weak_ptr&>>> - virtual_ptr& operator=( - const virtual_ptr, Registry>& other) { + weak_virtual_ptr& operator=( + const weak_virtual_ptr& other) { vp = other.vp; obj = other.obj; return *this; @@ -320,8 +365,8 @@ class virtual_ptr, Registry, void> { class Other, typename = std::enable_if_t&, std::weak_ptr&&>>> - virtual_ptr& operator=( - virtual_ptr, Registry>&& other) { + weak_virtual_ptr& operator=( + weak_virtual_ptr&& other) noexcept { vp = std::exchange( other.vp, detail::box_vptr(detail::null_vptr)); obj = std::move(other.obj); @@ -342,12 +387,11 @@ class virtual_ptr, Registry, void> { //! `const std::shared_ptr&`. template< class Other, - typename = std::enable_if_t< - BOOST_OPENMETHOD_UNLESS_MRDOCS(detail::) - IsPolymorphic && - std::is_assignable_v< - std::weak_ptr&, const std::shared_ptr&>>> - virtual_ptr& operator=(const std::shared_ptr& other) { + typename = std::enable_if_t>, + typename = std::enable_if_t&, const std::shared_ptr&>>> + weak_virtual_ptr& operator=(const std::shared_ptr& other) { vp = vptr_of(other); obj = other; return *this; @@ -368,14 +412,12 @@ class virtual_ptr, Registry, void> { //! `const std::weak_ptr&`. template< class Other, - typename = std::enable_if_t< - BOOST_OPENMETHOD_UNLESS_MRDOCS(detail::) - IsPolymorphic && - std::is_assignable_v< - std::weak_ptr&, const std::weak_ptr&>>> - virtual_ptr& operator=(const std::weak_ptr& other) { - vp = vptr_of(other.lock()); - obj = other; + typename = std::enable_if_t>, + typename = std::enable_if_t&, const std::weak_ptr&>>> + weak_virtual_ptr& operator=(const std::weak_ptr& other) { + assign(other); return *this; } @@ -390,12 +432,14 @@ class virtual_ptr, Registry, void> { //! @return A `shared_virtual_ptr` to the object if it still exists, or an //! empty `shared_virtual_ptr` with a `nullptr` v-table pointer otherwise. auto lock() const -> virtual_ptr, Registry> { + using shared = virtual_ptr, Registry>; + if (auto locked = obj.lock()) { - return virtual_ptr, Registry>( + return detail::virtual_ptr_access::make( std::move(locked), vp); } - return virtual_ptr, Registry>(); + return shared(); } //! Check whether the object still exists @@ -413,6 +457,37 @@ class virtual_ptr, Registry, void> { return obj.use_count(); } + //! Compare owners with a `weak_virtual_ptr` + //! + //! Provide the owner-based ordering that an associative container keyed on + //! `weak_virtual_ptr` needs. Note that `std::owner_less` accepts + //! `std::shared_ptr` and `std::weak_ptr` alone in some implementations, so + //! the comparator is best written as a function object calling + //! `owner_before`. + //! + //! @param other A `weak_virtual_ptr`. + //! + //! @return The result of `std::weak_ptr::owner_before` applied to the + //! `std::weak_ptr` held by `other`. + template + auto owner_before( + const weak_virtual_ptr& other) const noexcept -> bool { + return obj.owner_before(other.obj); + } + + //! Compare owners with a `shared_virtual_ptr` + //! + //! @param other A `shared_virtual_ptr`. + //! + //! @return The result of `std::weak_ptr::owner_before` applied to the + //! `std::shared_ptr` held by `other`. + template + auto owner_before( + const virtual_ptr, Registry>& other) + const noexcept -> bool { + return obj.owner_before(other.pointer()); + } + //! Release the reference to the object //! //! Reset the `std::weak_ptr`. Set the v-table pointer to `nullptr`. @@ -421,8 +496,19 @@ class virtual_ptr, Registry, void> { vp = detail::box_vptr(detail::null_vptr); } + //! Swap with another `weak_virtual_ptr` + //! + //! @param other A `weak_virtual_ptr` to the same class. + void swap(weak_virtual_ptr& other) noexcept { + std::swap(vp, other.vp); + obj.swap(other.obj); + } + //! Get the weak pointer to the object //! + //! @par Example + //! include:smart_pointers.cpp#classes;weak_pointer + //! //! @return A const reference to the `std::weak_ptr` auto pointer() const noexcept -> const std::weak_ptr& { return obj; @@ -430,21 +516,37 @@ class virtual_ptr, Registry, void> { //! Get the v-table pointer //! - //! @return A pointer to the v-table remembered when the `virtual_ptr` was - //! created or assigned, or `nullptr`. + //! @return A pointer to the v-table remembered when the `weak_virtual_ptr` + //! was created or assigned, or `nullptr`. auto vptr() const { return detail::unbox_vptr(this->vp); } }; -//! Alias for a `virtual_ptr>`. +//! Reject a `virtual_ptr` to a `std::weak_ptr` //! -//! @par Example -//! include:smart_pointers.cpp#weak_virtual_ptr_alias +//! A `std::weak_ptr` may refer to an object that no longer exists, so a +//! `virtual_ptr` cannot track an object through one. This specialization +//! rejects the combination at compile time, which also covers +//! @ref final_virtual_ptr, since that instantiates the `virtual_ptr` it +//! returns. Use @ref weak_virtual_ptr instead. //! -//! @see [Smart Pointers](xref:ROOT:smart_pointers.adoc) -template -using weak_virtual_ptr = virtual_ptr, Registry>; +//! The specialization steps aside if `virtual_traits` is specialized for +//! `std::weak_ptr`. +//! +//! @tparam Class The class pointed to by the `std::weak_ptr`. +//! @tparam Registry A @ref registry. +template +class virtual_ptr< + std::weak_ptr, Registry, + std::enable_if_t< + BOOST_OPENMETHOD_UNLESS_MRDOCS(detail::) + IsSmartPtr, Registry> == false>> { + static_assert( + detail::false_t, + "a std::weak_ptr cannot be wrapped in a virtual_ptr; use " + "weak_virtual_ptr"); +}; namespace aliases { using boost::openmethod::weak_virtual_ptr; diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 85954411..7ecbcfa6 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -192,7 +192,14 @@ foreach(compile_fail_cpp ${compile_fail_cpp_files}) message(FATAL_ERROR "${testname}.cpp has no `// expected-error: ` comment") endif() - openmethod_compile_fail_test(${testname} "${CMAKE_MATCH_1}") + set(fail_regex "${CMAKE_MATCH_1}") + # PASS_REGULAR_EXPRESSION is a list: a `;` would split the regex into + # alternatives, and the test would pass on either half. + if (fail_regex MATCHES ";") + message(FATAL_ERROR + "${testname}.cpp: the expected-error regex contains a `;`; use `.*`") + endif() + openmethod_compile_fail_test(${testname} "${fail_regex}") endforeach() if (TARGET Boost::dll) diff --git a/test/compile_fail_final_virtual_ptr_weak_ptr.cpp b/test/compile_fail_final_virtual_ptr_weak_ptr.cpp new file mode 100644 index 00000000..de996e59 --- /dev/null +++ b/test/compile_fail_final_virtual_ptr_weak_ptr.cpp @@ -0,0 +1,26 @@ +// Copyright (c) 2017-2026 Jean-Louis Leroy +// Distributed under the Boost Software License, Version 1.0. +// See accompanying file LICENSE_1_0.txt +// or copy at http://www.boost.org/LICENSE_1_0.txt) + +// Expected diagnostic, as a CMake regex (see CMakeLists.txt). +// expected-error: cannot be wrapped in a virtual_ptr + +#include +#include + +using namespace boost::openmethod; + +struct Animal { + virtual ~Animal() { + } +}; + +BOOST_OPENMETHOD_CLASSES(Animal); + +int main() { + auto felix = std::make_shared(); + std::weak_ptr weak = felix; + auto p = final_virtual_ptr(weak); + return 0; +} diff --git a/test/compile_fail_weak_ptr_parameter.cpp b/test/compile_fail_weak_ptr_parameter.cpp index fa645b23..69c79f9a 100644 --- a/test/compile_fail_weak_ptr_parameter.cpp +++ b/test/compile_fail_weak_ptr_parameter.cpp @@ -4,7 +4,8 @@ // or copy at http://www.boost.org/LICENSE_1_0.txt) // Expected diagnostic, as a CMake regex (see CMakeLists.txt). -// expected-error: a weak pointer cannot be a virtual parameter; call lock\(\) first +// A `;` would split the regex into two alternatives (see CMakeLists.txt). +// expected-error: a weak pointer cannot be a virtual parameter.*call lock\(\) first #include #include diff --git a/test/compile_fail_weak_virtual_ptr_parameter.cpp b/test/compile_fail_weak_virtual_ptr_parameter.cpp index 1a3666c5..99cb2d3b 100644 --- a/test/compile_fail_weak_virtual_ptr_parameter.cpp +++ b/test/compile_fail_weak_virtual_ptr_parameter.cpp @@ -4,7 +4,8 @@ // or copy at http://www.boost.org/LICENSE_1_0.txt) // Expected diagnostic, as a CMake regex (see CMakeLists.txt). -// expected-error: a weak pointer cannot be a virtual parameter; call lock\(\) first +// A `;` would split the regex into two alternatives (see CMakeLists.txt). +// expected-error: a weak pointer cannot be a virtual parameter.*call lock\(\) first #include #include @@ -17,7 +18,7 @@ struct Animal { }; struct Cat : Animal {}; -BOOST_OPENMETHOD(poke, (weak_virtual_ptr), void); +BOOST_OPENMETHOD(poke, (virtual_>), void); BOOST_OPENMETHOD_OVERRIDE(poke, (weak_virtual_ptr), void) { } diff --git a/test/test_adl_registry_smart_ptr.cpp b/test/test_adl_registry_smart_ptr.cpp index 2137433f..b51d4454 100644 --- a/test/test_adl_registry_smart_ptr.cpp +++ b/test/test_adl_registry_smart_ptr.cpp @@ -13,6 +13,7 @@ #include #include #include +#include #include #include @@ -59,6 +60,11 @@ static_assert(std::is_same_v< boost_intrusive_virtual_ptr, virtual_ptr, zoo_registry>>); +// weak_virtual_ptr is not a virtual_ptr, but it defaults its registry the +// same way, so it converts to and from the shared_virtual_ptr of its class +static_assert( + std::is_same_v, weak_virtual_ptr>); + BOOST_OPENMETHOD_CLASSES(Animal, Dog, zoo_registry); BOOST_OPENMETHOD(name, (shared_virtual_ptr), std::string); @@ -76,4 +82,9 @@ BOOST_AUTO_TEST_CASE(factories_need_no_registry_argument) { auto owned = make_unique_virtual(); static_assert(std::is_same_v>); + + weak_virtual_ptr observer = dog; + static_assert( + std::is_same_v>); + BOOST_TEST(name(observer.lock()) == "dog"); } diff --git a/test/test_weak_virtual_ptr.cpp b/test/test_weak_virtual_ptr.cpp index 9feb1873..2ebe7a70 100644 --- a/test/test_weak_virtual_ptr.cpp +++ b/test/test_weak_virtual_ptr.cpp @@ -11,12 +11,18 @@ #include "test_virtual_ptr_value_semantics.hpp" #include +#include #include #include -// A weak virtual_ptr is neither a smart virtual_ptr in the `IsSmartPtr` sense -// (no `virtual_traits`, no `rebind`) nor a plain one. +// A weak virtual_ptr is not a virtual_ptr at all: neither a smart one in the +// `IsSmartPtr` sense (no `virtual_traits`, no `rebind`) nor a plain one. static_assert(!IsSmartPtr, default_registry>); +static_assert(!is_virtual_ptr>); + +// Moves are noexcept, so containers relocate by moving, not copying. +static_assert(std::is_nothrow_move_constructible_v>); +static_assert(std::is_nothrow_move_assignable_v>); static_assert(std::is_same_v::element_type, Animal>); static_assert(std::is_same_v< @@ -137,6 +143,27 @@ BOOST_AUTO_TEST_CASE_TEMPLATE( BOOST_TEST(weak.expired()); BOOST_TEST(weak.vptr() == nullptr); BOOST_TEST(weak.lock().get() == nullptr); + BOOST_TEST(weak.use_count() == 0); + } + + { + // an expired source of the same class keeps its control block, so it + // keeps its owner identity. Across a class conversion that is up to + // the standard library: libstdc++ shares ownership, as + // [util.smartptr.weak.const] requires of a source that is expired but + // not empty; libc++ locks first, and an expired source then yields an + // empty weak pointer. + std::weak_ptr std_weak; + { + auto dead = std::make_shared(); + std_weak = dead; + } + + weak_virtual_ptr weak(std_weak); + BOOST_TEST(weak.expired()); + BOOST_TEST(weak.vptr() == nullptr); + BOOST_TEST(!weak.pointer().owner_before(std_weak)); + BOOST_TEST(!std_weak.owner_before(weak.pointer())); } { @@ -261,6 +288,59 @@ BOOST_AUTO_TEST_CASE_TEMPLATE( BOOST_TEST(source.expired()); BOOST_TEST(source.vptr() == nullptr); } + + { + auto felix = std::make_shared(); + weak_virtual_ptr weak_cat(felix); + weak_virtual_ptr weak_animal(weak_dog); + weak_cat.swap(weak_animal); + BOOST_TEST(weak_cat.lock().get() == snoopy.get()); + BOOST_TEST(weak_cat.vptr() == Registry::template static_vptr); + BOOST_TEST(weak_animal.lock().get() == felix.get()); + BOOST_TEST(weak_animal.vptr() == Registry::template static_vptr); + } +} + +// `std::owner_less` is generic only in libstdc++; MSVC's and libc++'s +// have overloads for `std::shared_ptr` and `std::weak_ptr` alone, so a +// container keyed on owner identity carries its own comparator. +struct owner_less { + template + auto operator()(const Left& left, const Right& right) const -> bool { + return left.owner_before(right); + } +}; + +BOOST_AUTO_TEST_CASE_TEMPLATE( + weak_virtual_ptr_owner_before, Registry, test_policies) { + init_test(); + + auto snoopy = std::make_shared(); + auto felix = std::make_shared(); + shared_virtual_ptr shared_dog(snoopy); + weak_virtual_ptr weak_dog(shared_dog); + weak_virtual_ptr weak_dog_too(snoopy); + weak_virtual_ptr weak_cat(felix); + + // same owner, against a shared and a weak virtual_ptr, to another class + BOOST_TEST(!weak_dog.owner_before(shared_dog)); + BOOST_TEST(!weak_dog.owner_before(weak_dog_too)); + BOOST_TEST(!weak_dog_too.owner_before(weak_dog)); + + // different owners: a strict weak ordering, the same as std::weak_ptr's + BOOST_TEST( + weak_dog.owner_before(weak_cat) != weak_cat.owner_before(weak_dog)); + BOOST_TEST( + weak_dog.owner_before(weak_cat) == + std::weak_ptr(snoopy).owner_before(felix)); + + std::set, owner_less> observers; + observers.insert(weak_dog); + observers.insert(weak_cat); + observers.insert(weak_virtual_ptr(snoopy)); // same owner + BOOST_TEST(observers.size() == 2u); + BOOST_TEST(observers.count(weak_dog) == 1u); + BOOST_TEST(observers.count(weak_cat) == 1u); } BOOST_AUTO_TEST_CASE_TEMPLATE( @@ -292,6 +372,52 @@ auto poke_cat(shared_virtual_ptr) -> std::string { return "hiss"; } +struct BOOST_OPENMETHOD_ID(observe); + +// A weak_virtual_ptr is not a virtual_ptr, so it can be an ordinary, non-virtual +// parameter of a method: the object it tracks is not dispatched on. +template +auto observe_dog( + virtual_ptr, weak_virtual_ptr other) + -> std::string { + return other.expired() ? "dog sees nobody" : "dog sees somebody"; +} + +template +auto observe_cat( + virtual_ptr, weak_virtual_ptr other) + -> std::string { + return other.expired() ? "cat sees nobody" : "cat sees somebody"; +} + +BOOST_AUTO_TEST_CASE_TEMPLATE( + weak_virtual_ptr_non_virtual_parameter, Registry, test_policies) { + using observe = method< + BOOST_OPENMETHOD_ID(observe), + auto(virtual_ptr, weak_virtual_ptr) + ->std::string, + Registry>; + + BOOST_OPENMETHOD_REGISTER( + typename observe::template override< + observe_dog, observe_cat>); + + init_test(); + + auto snoopy = std::make_shared(); + auto felix = std::make_shared(); + virtual_ptr dog(*snoopy); + virtual_ptr cat(*felix); + weak_virtual_ptr weak_dog(snoopy); + weak_virtual_ptr weak_cat(felix); + + BOOST_TEST(observe::fn(dog, weak_cat) == "dog sees somebody"); + BOOST_TEST(observe::fn(cat, weak_dog) == "cat sees somebody"); + + felix.reset(); + BOOST_TEST(observe::fn(dog, weak_cat) == "dog sees nobody"); +} + BOOST_AUTO_TEST_CASE_TEMPLATE( weak_virtual_ptr_dispatch, Registry, test_policies) { using poke = method<