Hello,The attached patch changes std::weak_ptr "converting move constructor/assignment" -- that is, from a rvalue weak_ptr<Derived> to a weak_ptr<Base>.
In the general case, such conversion requires lock()ing the weak_ptr because the pointed-to object might have already been destroyed, and a Derived->Base pointer conversion requires dereferencing the pointer iff Base is a virtual base class of Derived. Therefore the correct thing to do in the general case is to lock() the weak_ptr, call get() on the resulting shared_ptr, and do the pointer conversion while this shared_ptr is alive.
However, the newly added __is_virtual_base_of builtin allows us to micro-optimize this pointer upcast, and avoid calling lock() (and paying for the associated atomic operations) in case Base is *not* a virtual base class of Derived. In this case we can "just" perform the upcast; as far as I know, no ABI requires dereferencing the pointer in the non-virtual inheritance case.
Thanks, -- Giuseppe D'Angelo
From c67349ec3ac53573a5a36edeedaea9e8f5e41c97 Mon Sep 17 00:00:00 2001 From: Giuseppe D'Angelo <giuseppe.dang...@kdab.com> Date: Wed, 2 Oct 2024 18:14:34 +0200 Subject: [PATCH] libstdc++: use is_virtual_base_of in std::weak_ptr operations 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. 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. * testsuite/20_util/weak_ptr/cons/virtual_bases.cc: New test. Signed-off-by: Giuseppe D'Angelo <giuseppe.dang...@kdab.com> --- libstdc++-v3/include/bits/shared_ptr_base.h | 19 +++-- .../20_util/weak_ptr/cons/virtual_bases.cc | 80 +++++++++++++++++++ 2 files changed, 94 insertions(+), 5 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 ee01594ce0c..a4b52ae4565 100644 --- a/libstdc++-v3/include/bits/shared_ptr_base.h +++ b/libstdc++-v3/include/bits/shared_ptr_base.h @@ -1992,6 +1992,15 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION template<typename _Yp> using _Assignable = _Compatible<_Yp, __weak_ptr&>; + // Helper for construction/assignment: + template<typename _Yp, typename = _Compatible<_Yp>> + static _Tp* __safe_upcast(const __weak_ptr<_Yp, _Lp> &__r) + { + if _GLIBCXX17_CONSTEXPR (__builtin_is_virtual_base_of(_Tp, _Yp)) + return __r.lock().get(); + return __r._M_ptr; + } + public: using element_type = typename remove_extent<_Tp>::type; @@ -2019,8 +2028,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(__safe_upcast(__r)), _M_refcount(__r._M_refcount) + { } template<typename _Yp, typename = _Compatible<_Yp>> __weak_ptr(const __shared_ptr<_Yp, _Lp>& __r) noexcept @@ -2033,7 +2042,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(__safe_upcast(__r)), _M_refcount(std::move(__r._M_refcount)) { __r._M_ptr = nullptr; } __weak_ptr& @@ -2043,7 +2052,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION _Assignable<_Yp> operator=(const __weak_ptr<_Yp, _Lp>& __r) noexcept { - _M_ptr = __r.lock().get(); + _M_ptr = __safe_upcast(__r); _M_refcount = __r._M_refcount; return *this; } @@ -2068,7 +2077,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION _Assignable<_Yp> operator=(__weak_ptr<_Yp, _Lp>&& __r) noexcept { - _M_ptr = __r.lock().get(); + _M_ptr = __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.34.1
smime.p7s
Description: S/MIME Cryptographic Signature