On Mon, 8 Sept 2025 at 15:19, Tomasz Kamiński <[email protected]> wrote:
>
> From: Giuseppe D'Angelo <[email protected]>
>
> Converting a weak_ptr<Derived> to a weak_ptr<Base> requires calling
> lock() on the source object in the general case.
>
> Although the source weak_ptr<Derived> does contain a raw pointer to
> Derived, we can't just get it and (up)cast it to Base, as that will
> dereference the pointer in case Base is a virtual base class of Derived.
>
> We don't know if the managed object is still alive, and therefore if
> this operation is safe to do; we therefore temporarily lock() the source
> weak_ptr, do the cast using the resulting shared_ptr, and then discard
> this shared_ptr. Simply checking the strong counter isn't sufficient,
> because if multiple threads are involved then we'd have a race / TOCTOU
> problem; the object may get destroyed after we check the strong counter
> and before we cast the pointer.
>
> However lock() is not necessary if we know that Base is *not* a virtual
> base class of Derived; in this case we can avoid the relatively
> expensive call to lock() and just cast the pointer. This commit uses
> the newly added builtin to detect this case and optimize std::weak_ptr's
> converting constructors and assignment operations.
>
> Apart from non-virtual bases, there's also another couple of interesting
> cases where we can also avoid locking. Specifically:
>
> 1) converting a weak_ptr<T[N]> to a weak_ptr<T cv[]>;
> 2) converting a weak_ptr<T*> to a weak_ptr<T const * const> or similar.
>
> Since this logic is going to be used by multiple places, I've
> centralized it in a new static helper.
>
> libstdc++-v3/ChangeLog:
>
> * include/bits/shared_ptr_base.h (__weak_ptr): Avoid calling
> lock() when converting or assigning a weak_ptr<Derived> to
> a weak_ptr<Base> in case Base is not a virtual base of Derived.
> This logic is centralized in _S_safe_upcast, called by the
> various converting constructors/assignment operators.
> (_S_safe_upcast): New helper function.
> * testsuite/20_util/weak_ptr/cons/virtual_bases.cc: New test.
>
> Reviewed-by: Jonathan Wakely <[email protected]>
> Reviewed-by: Tomasz Kamiński <[email protected]>
> Signed-off-by: Giuseppe D'Angelo <[email protected]>
> ---
> This was reviewed on forge:
> https://forge.sourceware.org/gcc/gcc-TEST/pulls/41,
> but we have decided to wait until v16 to merge it.
> I have rebased it since, without any issues, and added a line break
> before _S_safe_upcast.
>
> Tested on x86_64-linux locally and powerpc64le-linux.
> OK for trunk?
OK, thanks (and thanks, Giuseppe for the idea and implementation).
> libstdc++-v3/include/bits/shared_ptr_base.h | 53 ++++++++++--
> .../20_util/weak_ptr/cons/virtual_bases.cc | 80 +++++++++++++++++++
> 2 files changed, 126 insertions(+), 7 deletions(-)
> create mode 100644
> libstdc++-v3/testsuite/20_util/weak_ptr/cons/virtual_bases.cc
>
> diff --git a/libstdc++-v3/include/bits/shared_ptr_base.h
> b/libstdc++-v3/include/bits/shared_ptr_base.h
> index fb868e7afc3..a961c02e979 100644
> --- a/libstdc++-v3/include/bits/shared_ptr_base.h
> +++ b/libstdc++-v3/include/bits/shared_ptr_base.h
> @@ -2012,6 +2012,10 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> template<typename _Tp, _Lock_policy _Lp>
> class __weak_ptr
> {
> + public:
> + using element_type = typename remove_extent<_Tp>::type;
> +
> + private:
> template<typename _Yp, typename _Res = void>
> using _Compatible = typename
> enable_if<__sp_compatible_with<_Yp*, _Tp*>::value, _Res>::type;
> @@ -2020,9 +2024,44 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> template<typename _Yp>
> using _Assignable = _Compatible<_Yp, __weak_ptr&>;
>
> - public:
> - using element_type = typename remove_extent<_Tp>::type;
> +#pragma GCC diagnostic push
> +#pragma GCC diagnostic ignored "-Wc++17-extensions" // if constexpr
> + // Helper for construction/assignment:
> + template<typename _Yp>
> + static element_type*
> + _S_safe_upcast(const __weak_ptr<_Yp, _Lp>& __r)
> + {
> + // We know that _Yp and _Tp are compatible, that is, either
> + // _Yp* is convertible to _Tp* or _Yp is U[N] and _Tp is U cv [].
> +
> + // If _Yp is the same as _Tp after removing extents and cv
> + // qualifications, there's no pointer adjustments to do. This
> + // also allows us to support incomplete types.
> + using _At = typename remove_cv<typename
> remove_extent<_Tp>::type>::type;
> + using _Bt = typename remove_cv<typename
> remove_extent<_Yp>::type>::type;
> + if constexpr (is_same<_At, _Bt>::value)
> + return __r._M_ptr;
> + // If they're not the same type, but they're both scalars,
> + // we again don't need any adjustment. This allows us to support
> e.g.
> + // pointers to a differently cv qualified type X.
> + else if constexpr (__and_<is_scalar<_At>, is_scalar<_Bt>>::value)
> + return __r._M_ptr;
> +#if _GLIBCXX_USE_BUILTIN_TRAIT(__builtin_is_virtual_base_of)
> + // If _Tp is not a virtual base class of _Yp, the pointer
> + // conversion does not require dereferencing __r._M_ptr; just
> + // rely on the implicit conversion.
> + else if constexpr (!__builtin_is_virtual_base_of(_Tp, _Yp))
> + return __r._M_ptr;
> +#endif
> + // Expensive path; must lock() and do the pointer conversion while
> + // a shared_ptr keeps the pointee alive (because we may need
> + // to dereference).
> + else
> + return __r.lock().get();
> + }
> +#pragma GCC diagnostic pop
>
> + public:
> constexpr __weak_ptr() noexcept
> : _M_ptr(nullptr), _M_refcount()
> { }
> @@ -2047,8 +2086,8 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> // in multithreaded programs __r._M_ptr may be invalidated at any
> point.
> template<typename _Yp, typename = _Compatible<_Yp>>
> __weak_ptr(const __weak_ptr<_Yp, _Lp>& __r) noexcept
> - : _M_refcount(__r._M_refcount)
> - { _M_ptr = __r.lock().get(); }
> + : _M_ptr(_S_safe_upcast(__r)), _M_refcount(__r._M_refcount)
> + { }
>
> template<typename _Yp, typename = _Compatible<_Yp>>
> __weak_ptr(const __shared_ptr<_Yp, _Lp>& __r) noexcept
> @@ -2061,7 +2100,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
>
> template<typename _Yp, typename = _Compatible<_Yp>>
> __weak_ptr(__weak_ptr<_Yp, _Lp>&& __r) noexcept
> - : _M_ptr(__r.lock().get()), _M_refcount(std::move(__r._M_refcount))
> + : _M_ptr(_S_safe_upcast(__r)), _M_refcount(std::move(__r._M_refcount))
> { __r._M_ptr = nullptr; }
>
> __weak_ptr&
> @@ -2071,7 +2110,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> _Assignable<_Yp>
> operator=(const __weak_ptr<_Yp, _Lp>& __r) noexcept
> {
> - _M_ptr = __r.lock().get();
> + _M_ptr = _S_safe_upcast(__r);
> _M_refcount = __r._M_refcount;
> return *this;
> }
> @@ -2096,7 +2135,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> _Assignable<_Yp>
> operator=(__weak_ptr<_Yp, _Lp>&& __r) noexcept
> {
> - _M_ptr = __r.lock().get();
> + _M_ptr = _S_safe_upcast(__r);
> _M_refcount = std::move(__r._M_refcount);
> __r._M_ptr = nullptr;
> return *this;
> diff --git a/libstdc++-v3/testsuite/20_util/weak_ptr/cons/virtual_bases.cc
> b/libstdc++-v3/testsuite/20_util/weak_ptr/cons/virtual_bases.cc
> new file mode 100644
> index 00000000000..ac3e4bce9da
> --- /dev/null
> +++ b/libstdc++-v3/testsuite/20_util/weak_ptr/cons/virtual_bases.cc
> @@ -0,0 +1,80 @@
> +// { dg-do run { target c++11 } }
> +
> +#include <memory>
> +#include <testsuite_hooks.h>
> +
> +struct BaseBase { virtual ~BaseBase() = default; };
> +struct Base : BaseBase { virtual ~Base() = default; };
> +struct Derived1 : Base { virtual ~Derived1() = default; };
> +struct Derived2 : virtual Base { virtual ~Derived2() = default; };
> +struct Derived3 : virtual Base { virtual ~Derived3() = default; };
> +struct Derived4 : Derived2, Derived3 { virtual ~Derived4() = default; };
> +struct Derived5 : Derived4 { virtual ~Derived5() = default; };
> +
> +template<typename T>
> +void test01()
> +{
> + std::shared_ptr<T> ptr(new T);
> + VERIFY(ptr);
> +
> + std::weak_ptr<T> wptr1 = ptr;
> + VERIFY(wptr1.lock());
> +
> + std::weak_ptr<Base> wptr2 = ptr;
> + VERIFY(wptr2.lock());
> +
> + std::weak_ptr<Base> wptr3 = wptr1;
> + VERIFY(wptr3.lock());
> +
> + std::weak_ptr<BaseBase> wptr4 = ptr;
> + VERIFY(wptr4.lock());
> +
> + std::weak_ptr<BaseBase> wptr5 = std::move(wptr1);
> + VERIFY(wptr5.lock());
> +
> + ptr.reset();
> +
> + VERIFY(!wptr1.lock());
> + VERIFY(!wptr2.lock());
> + VERIFY(!wptr3.lock());
> + VERIFY(!wptr4.lock());
> + VERIFY(!wptr5.lock());
> +}
> +
> +template<typename T>
> +void test02()
> +{
> + std::shared_ptr<T> ptr(new T);
> + VERIFY(ptr);
> +
> + std::weak_ptr<T> wptr1 = ptr;
> + VERIFY(wptr1.lock());
> +
> + std::weak_ptr<Base> wptr2 = ptr;
> + VERIFY(wptr2.lock());
> +
> + ptr.reset();
> +
> + std::weak_ptr<Base> wptr3 = wptr1;
> + std::weak_ptr<BaseBase> wptr4 = wptr1;
> + std::weak_ptr<BaseBase> wptr5 = std::move(wptr1);
> +
> + VERIFY(!wptr1.lock());
> + VERIFY(!wptr2.lock());
> + VERIFY(!wptr3.lock());
> + VERIFY(!wptr4.lock());
> + VERIFY(!wptr5.lock());
> +}
> +
> +int main()
> +{
> + test01<Derived1>();
> + test01<Derived2>();
> + test01<Derived4>();
> + test01<Derived5>();
> +
> + test02<Derived1>();
> + test02<Derived2>();
> + test02<Derived4>();
> + test02<Derived5>();
> +}
> --
> 2.51.0
>