On Mon, 8 Sept 2025 at 15:19, Tomasz Kamiński <tkami...@redhat.com> wrote:
>
> From: Giuseppe D'Angelo <giuseppe.dang...@kdab.com>
>
> 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 <jwak...@redhat.com>
> Reviewed-by: Tomasz Kamiński <tkami...@redhat.com>
> Signed-off-by: Giuseppe D'Angelo <giuseppe.dang...@kdab.com>
> ---
> 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
>

Reply via email to