#pragma once // Author: Rahil Baber // Date: 2019/03/17 // // A custom shared_ptr implementation called stoppable_ptr. Has a threadsafe stop() member function that // will stop further copies being made (a resetted stoppable_ptr is returned instead). The stop() function // acts atomically to ensure after it is called the use_count can only decrease. The is_stopped() function // can be used to query if stop() has been called. // // Notable supported features include: // an aliasing constructor, // support for array allocation, // weak_stoppable_ptr, // enable_stoppable_from_this, // static_pointer_cast(), dynamic_pointer_cast(), const_pointer_cast(), reinterpret_pointer_cast(), // make_stoppable(), // support for custom deleters, // construction from unique_ptr // // Notable differences to shared_ptr: // stop() and is_stopped() member functions in stoppable_ptr and weak_stoppable_ptr, // no support for custom allocators (this may be added in the future), // make_stoppable() will not create the control block and underlying resource in one memory allocation (this // was purposefully done to make when the resource cleanup happens independent of how the stoppable_ptr was // created). #include #include #include #include #include #include namespace util { // Forward declare. template class stoppable_ptr; namespace priv_impl_stoppable_ptr { class ControlBlockBase { public: virtual ~ControlBlockBase() = default; std::mutex mtx; bool stopped; std::uint64_t strongRefCount; std::uint64_t weakRefCount; bool IsStopped() noexcept { std::lock_guard lock(mtx); return stopped; } void Stop() noexcept { std::lock_guard lock(mtx); stopped = true; } // Returns true if should cleanup the control block. virtual bool DecStrongRef() noexcept = 0; // Returns true if strongRefCount was increased. bool IncStrongRef() noexcept { std::lock_guard lock(mtx); if (strongRefCount == 0 || stopped) return false; strongRefCount++; return true; } // Returns true if weakRefCount was increased. bool IncWeakRef() noexcept { std::lock_guard lock(mtx); if (strongRefCount == 0 || stopped) return false; weakRefCount++; return true; } // Returns true if should cleanup the control block. bool DecWeakRef() noexcept { std::lock_guard lock(mtx); weakRefCount--; if (weakRefCount == 0 && strongRefCount == 0) return true; return false; } }; template class ControlBlockDerived : public ControlBlockBase { public: T* resource; std::function deleter; virtual bool DecStrongRef() noexcept override { bool delCtrlBlock = false; bool delResource = false; T* resourceCopy; { std::lock_guard lock(mtx); strongRefCount--; if (strongRefCount == 0) { // Delete the resource outside of the locked scope. delResource = true; resourceCopy = resource; resource = nullptr; if (weakRefCount == 0) delCtrlBlock = true; } } if (delResource) deleter(resourceCopy); return delCtrlBlock; } }; template class HelperSetWeakThis { public: static void SetWeakThis(T* ptr, const stoppable_ptr& sp) {} }; template class HelperSetWeakThis>> { public: static void SetWeakThis(T* ptr, const stoppable_ptr& sp) { ptr->weak_this = sp; } }; } // End of namespace priv_impl_stoppable_ptr. // Forward declare. template class weak_stoppable_ptr; template class enable_stoppable_from_this; template class stoppable_ptr { public: constexpr stoppable_ptr() noexcept : ptr{nullptr}, ctrl{nullptr} {} constexpr stoppable_ptr(std::nullptr_t) noexcept : stoppable_ptr{} {} template explicit stoppable_ptr(U* ptr) { Constructor(ptr, [](T* ptr){ delete ptr; }); } template stoppable_ptr(U* ptr, Deleter d) { Constructor(ptr, [del=std::move(d)](T* ptr){ del(ptr); }); } template stoppable_ptr(std::nullptr_t, Deleter d) { Constructor(nullptr, [del=std::move(d)](T* ptr){ del(ptr); }); } template stoppable_ptr(const stoppable_ptr& r, T* ptr) noexcept { if (r.ctrl && r.ctrl->IncStrongRef()) { ctrl = r.ctrl; this->ptr = ptr; return; } ctrl = nullptr; this->ptr = nullptr; return; } template stoppable_ptr(stoppable_ptr&& r, T* ptr) noexcept { if (r.ctrl) { ctrl = r.ctrl; this->ptr = ptr; r.ctrl = nullptr; r.ptr = nullptr; return; } ctrl = nullptr; this->ptr = nullptr; return; } stoppable_ptr(const stoppable_ptr& r) noexcept : stoppable_ptr{r, r.ptr} {} template stoppable_ptr(const stoppable_ptr& r) noexcept : stoppable_ptr{r, r.ptr} {} stoppable_ptr(stoppable_ptr&& r) noexcept : stoppable_ptr{std::move(r), r.ptr} {} template stoppable_ptr(stoppable_ptr&& r) noexcept : stoppable_ptr{std::move(r), r.ptr} {} template explicit stoppable_ptr(const weak_stoppable_ptr& r) noexcept; template stoppable_ptr(std::unique_ptr&& r) { Deleter d = r.get_deleter(); auto deleter = [del=std::move(d)](T* ptr){ del(ptr); }; Constructor(r.release(), deleter); } ~stoppable_ptr() { reset(); } stoppable_ptr& operator=(const stoppable_ptr& r) noexcept { return operator= (r); } template stoppable_ptr& operator=(const stoppable_ptr& r) noexcept { stoppable_ptr(r).swap(*this); return *this; } stoppable_ptr& operator=(stoppable_ptr&& r) noexcept { return operator= (std::move(r)); } template stoppable_ptr& operator=(stoppable_ptr&& r) noexcept { stoppable_ptr(std::move(r)).swap(*this); return *this; } template stoppable_ptr& operator=(std::unique_ptr&& r) { stoppable_ptr(std::move(r)).swap(*this); return *this; } T& operator*() const noexcept { return *ptr; } T* operator->() const noexcept { return ptr; } T& operator[](std::ptrdiff_t idx) const { return ptr[idx]; } explicit operator bool() const noexcept { return (ptr != nullptr); } void reset() noexcept { if (ptr) ptr = nullptr; if (!ctrl) return; if (ctrl->DecStrongRef()) delete ctrl; ctrl = nullptr; return; } template void reset(U* ptr) { stoppable_ptr(ptr).swap(*this); } template void reset(U* ptr, Deleter d) { stoppable_ptr(ptr, d).swap(*this); } T* get() const noexcept { return ptr; } std::uint64_t use_count() const noexcept { if (!ctrl) return 0; std::lock_guard lock(ctrl->mtx); return ctrl->strongRefCount; } void swap(stoppable_ptr& r) noexcept { if (ctrl != r.ctrl) std::swap(ctrl, r.ctrl); if (ptr != r.ptr) std::swap(ptr, r.ptr); return; } void stop() const noexcept { if (ctrl) ctrl->Stop(); } bool is_stopped() const noexcept { return ctrl ? ctrl->IsStopped() : false; } private: T* ptr; priv_impl_stoppable_ptr::ControlBlockBase* ctrl; void Constructor(T* ptr, std::function deleter) { priv_impl_stoppable_ptr::ControlBlockDerived* derivedCtrl = nullptr; try { derivedCtrl = new priv_impl_stoppable_ptr::ControlBlockDerived(); ctrl = derivedCtrl; derivedCtrl->stopped = false; derivedCtrl->resource = ptr; derivedCtrl->deleter = std::move(deleter); derivedCtrl->strongRefCount = 1; derivedCtrl->weakRefCount = 0; this->ptr = ptr; } catch(...) { deleter(ptr); delete derivedCtrl; throw; } try { priv_impl_stoppable_ptr::HelperSetWeakThis::SetWeakThis(ptr, *this); } catch(...) { derivedCtrl->deleter(ptr); delete derivedCtrl; throw; } } template friend class stoppable_ptr; template friend class weak_stoppable_ptr; }; template bool operator==(const stoppable_ptr& lhs, const stoppable_ptr& rhs) noexcept { return (lhs.get()==rhs.get()); } template bool operator!=(const stoppable_ptr& lhs, const stoppable_ptr& rhs) noexcept { return (lhs.get()!=rhs.get()); } template bool operator==(const stoppable_ptr& lhs, std::nullptr_t rhs) noexcept { return !lhs.get(); } template bool operator==(std::nullptr_t lhs, const stoppable_ptr& rhs) noexcept { return !rhs.get(); } template bool operator!=(const stoppable_ptr& lhs, std::nullptr_t rhs) noexcept { return lhs.get(); } template bool operator!=(std::nullptr_t lhs, const stoppable_ptr& rhs) noexcept { return rhs.get(); } template stoppable_ptr static_pointer_cast(const stoppable_ptr& r) noexcept { return stoppable_ptr(r, static_cast(r.get())); } template stoppable_ptr static_pointer_cast(stoppable_ptr&& r) noexcept { return stoppable_ptr(std::move(r), static_cast(r.get())); } template stoppable_ptr dynamic_pointer_cast(const stoppable_ptr& r) noexcept { T* p = dynamic_cast(r.get()); return p ? stoppable_ptr(r, p) : stoppable_ptr(); } template stoppable_ptr dynamic_pointer_cast(stoppable_ptr&& r) noexcept { T* p = dynamic_cast(r.get()); return p ? stoppable_ptr(std::move(r), p) : stoppable_ptr(); } template stoppable_ptr const_pointer_cast(const stoppable_ptr& r) noexcept { return stoppable_ptr(r, const_cast(r.get())); } template stoppable_ptr const_pointer_cast(stoppable_ptr&& r) noexcept { return stoppable_ptr(std::move(r), const_cast(r.get())); } template stoppable_ptr reinterpret_pointer_cast(const stoppable_ptr& r) noexcept { return stoppable_ptr(r, reinterpret_cast(r.get())); } template stoppable_ptr reinterpret_pointer_cast(stoppable_ptr&& r) noexcept { return stoppable_ptr(std::move(r), reinterpret_cast(r.get())); } namespace priv_impl_stoppable_ptr { template class MakeSelector { public: using SingleObjectRetType = stoppable_ptr; }; template class MakeSelector { public: using UnknownBoundRetType = stoppable_ptr; }; template class MakeSelector { public: using KnownBoundRetType = stoppable_ptr; }; } // End of namespace priv_impl_stoppable_ptr. template typename priv_impl_stoppable_ptr::MakeSelector::SingleObjectRetType make_stoppable(Args&&... args) { return stoppable_ptr(new T(std::forward(args)...)); } template typename priv_impl_stoppable_ptr::MakeSelector::UnknownBoundRetType make_stoppable(std::size_t N) { using U = typename std::remove_extent::type; return stoppable_ptr(new U[N], [](U* ptr){ delete[] ptr; }); } template typename priv_impl_stoppable_ptr::MakeSelector::KnownBoundRetType make_stoppable() { using U = typename std::remove_extent::type; std::size_t N = std::extent::value; return stoppable_ptr(new U[N], [](U* ptr){ delete[] ptr; }); } template class weak_stoppable_ptr { public: constexpr weak_stoppable_ptr() noexcept : ptr{nullptr}, ctrl{nullptr} {} weak_stoppable_ptr(const weak_stoppable_ptr& r) noexcept { Copy(r.ctrl, r.ptr); } template weak_stoppable_ptr(const weak_stoppable_ptr& r) noexcept { Copy(r.ctrl, r.ptr); } weak_stoppable_ptr(weak_stoppable_ptr&& r) noexcept { Move(std::move(r)); } template weak_stoppable_ptr(weak_stoppable_ptr&& r) noexcept { Move(std::move(r)); } template weak_stoppable_ptr(const stoppable_ptr& r) noexcept { Copy(r.ctrl, r.ptr); } ~weak_stoppable_ptr() { reset(); } weak_stoppable_ptr& operator=(const weak_stoppable_ptr& r) noexcept { return operator= (r); } template weak_stoppable_ptr& operator=(const weak_stoppable_ptr& r) noexcept { weak_stoppable_ptr(r).swap(*this); return *this; } weak_stoppable_ptr& operator=(weak_stoppable_ptr&& r) noexcept { return operator= (std::move(r)); } template weak_stoppable_ptr& operator=(weak_stoppable_ptr&& r) noexcept { weak_stoppable_ptr(std::move(r)).swap(*this) return *this; } template weak_stoppable_ptr& operator=(const stoppable_ptr& r) noexcept { weak_stoppable_ptr(r).swap(*this); return *this; } void reset() noexcept { if (ptr) ptr = nullptr; if (!ctrl) return; if (ctrl->DecWeakRef()) delete ctrl; ctrl = nullptr; return; } void stop() const noexcept { if (ctrl) ctrl->Stop(); } bool is_stopped() const noexcept { return ctrl ? ctrl->IsStopped() : false; } std::uint64_t use_count() const noexcept { if (!ctrl) return 0; std::lock_guard lock(ctrl->mtx); return ctrl->strongRefCount; } bool expired() const noexcept { if (!ctrl) return true; std::lock_guard lock(ctrl->mtx); return (ctrl->strongRefCount == 0 || ctrl->stopped); } void swap(weak_stoppable_ptr& r) noexcept { if (ctrl != r.ctrl) std::swap(ctrl, r.ctrl); if (ptr != r.ptr) std::swap(ptr, r.ptr); return; } stoppable_ptr lock() const noexcept { if (!ctrl || ctrl->IncStrongRef() == false) return stoppable_ptr(); stoppable_ptr out; out.ctrl = ctrl; out.ptr = ptr; return out; } private: T* ptr; priv_impl_stoppable_ptr::ControlBlockBase* ctrl; void Copy(priv_impl_stoppable_ptr::ControlBlockBase* c, T* p) noexcept { if (c && c->IncWeakRef()) { ctrl = c; ptr = p; return; } ctrl = nullptr; ptr = nullptr; return; } template void Move(weak_stoppable_ptr&& r) noexcept { if (r.ctrl) { ctrl = r.ctrl; ptr = r.ptr; r.ctrl = nullptr; r.ptr = nullptr; return; } ctrl = nullptr; ptr = nullptr; return; } template friend class stoppable_ptr; template friend class weak_stoppable_ptr; }; template template stoppable_ptr::stoppable_ptr(const weak_stoppable_ptr& r) noexcept { if (r.ctrl == nullptr || r.ctrl->IncStrongRef() == false) { ctrl = nullptr; ptr = nullptr; return; } ctrl = r.ctrl; ptr = r.ptr; return; } template class enable_stoppable_from_this { public: using priv_impl_esft_type = T; stoppable_ptr stoppable_from_this() { return stoppable_ptr(weak_this); } stoppable_ptr stoppable_from_this() const { return stoppable_ptr(weak_this); } weak_stoppable_ptr weak_stoppable_from_this() noexcept { return weak_this; } weak_stoppable_ptr weak_stoppable_from_this() const noexcept { return weak_this; } protected: constexpr enable_stoppable_from_this() noexcept : weak_this{} {} enable_stoppable_from_this(const enable_stoppable_from_this&) noexcept : weak_this{} {} ~enable_stoppable_from_this() = default; enable_stoppable_from_this& operator=(const enable_stoppable_from_this&) noexcept { return *this; } private: weak_stoppable_ptr weak_this; template friend class priv_impl_stoppable_ptr::HelperSetWeakThis; }; } // End of namespace util.