/* @file Refcounted.hpp */ #pragma once #include "xo/indentlog/scope.hpp" #include "xo/cxxutil/demangle.hpp" //#include #include #include namespace xo { namespace ref { template class intrusive_ptr; } template using rp = ref::intrusive_ptr; namespace ref { class Refcount; template class Borrow; /* originally used boost::instrusive_ptr<>. * ran into a bug. probably mine, but implemented * refcounting inline for debugging */ template class intrusive_ptr { public: using element_type = T; public: intrusive_ptr() : ptr_(nullptr) {} intrusive_ptr(T * x) : ptr_(x) { #ifdef XO_INTRUSIVE_PTR_ENABLE_LOGGING intrusive_ptr_log_ctor(sc_self_type, this, x); #endif intrusive_ptr_add_ref(ptr_); } /*ctor*/ /* NOTE: need exactly this form for copy-constructor * clang11 will not recognize template form below as * supplying copy ctor, and default version is broken for * instrusive_ptr. */ intrusive_ptr(intrusive_ptr const & x) : ptr_(x.get()) { #ifdef XO_INTRUSIVE_PTR_ENABLE_LOGGING intrusive_ptr_log_cctor(sc_self_type, this, x.get()); #endif intrusive_ptr_add_ref(ptr_); } /*cctor*/ /* create from instrusive pointer to some related type S */ template intrusive_ptr(intrusive_ptr const & x) : ptr_(x.get()) { #ifdef XO_INTRUSIVE_PTR_ENABLE_LOGGING intrusive_ptr_log_cctor(sc_self_type, this, x.get()); #endif intrusive_ptr_add_ref(ptr_); } /*cctor*/ /* move ctor -- in this case don't need to update refcount */ intrusive_ptr(intrusive_ptr && x) : ptr_{std::move(x.ptr_)} { #ifdef XO_INTRUSIVE_PTR_ENABLE_LOGGING intrusive_ptr_log_mctor(sc_self_type, this, ptr_); #endif /* since we're moving from x, need to make sure x dtor * doesn't decrement refcount */ x.ptr_ = nullptr; } /* aliasing ctor. see ctor (8) here: * [[https://en.cppreference.com/w/cpp/memory/shared_ptr/shared_ptr]] * and this dicsussion: * [[https://stackoverflow.com/questions/49178231/pybind11-multiple-inheritance-with-custom-holder-type-fails-to-cast-to-base-type/73131206#73131206]] */ template intrusive_ptr(intrusive_ptr const & /*x*/, element_type * y) : ptr_{y} { if (std::is_same()) { #ifdef XO_INTRUSIVE_PTR_ENABLE_LOGGING intrusive_ptr_log_actor(sc_self_type, this, y); #endif intrusive_ptr_add_ref(ptr_); ; /* trivial aliasing, proceed */ } else { using xo::xtag; throw std::runtime_error(tostr("attempt to use aliasing ctor with", xtag("Y", reflect::type_name()), xtag("T", reflect::type_name()))); } } /*ctor*/ ~intrusive_ptr() { T * x = this->ptr_; #ifdef XO_INTRUSIVE_PTR_ENABLE_LOGGING intrusive_ptr_log_dtor(sc_self_type, this, x); #endif this->ptr_ = nullptr; intrusive_ptr_release(x); } /*dtor*/ static bool compare(intrusive_ptr const & x, intrusive_ptr const & y) { return ptrdiff_t(x.get() - y.get()); } Borrow borrow() const; T * get() const { return ptr_; } T * operator->() const { return ptr_; } operator bool() const { return ptr_ != nullptr; } intrusive_ptr & operator=(intrusive_ptr const & rhs) { T * x = rhs.get(); #ifdef XO_INTRUSIVE_PTR_ENABLE_LOGGING intrusive_ptr_log_assign(sc_self_type, this, x); #endif T * old = this->ptr_; this->ptr_ = x; intrusive_ptr_add_ref(x); intrusive_ptr_release(old); return *this; } /*operator=*/ intrusive_ptr & operator=(intrusive_ptr && rhs) { #ifdef XO_INTRUSIVE_PTR_ENABLE_LOGGING intrusive_ptr_log_massign(sc_self_type, this, rhs.get()); #endif std::swap(this->ptr_, rhs.ptr_); /* dtor on rhs will decrement refcount on old value of this->ptr_ * don't increment for new value, since refcount just transfers from rhs to *this */ return *this; } /*operator=*/ private: static constexpr std::string_view sc_self_type = xo::reflect::type_name>(); private: T * ptr_ = nullptr; }; /*intrusive_ptr*/ template inline bool operator==(intrusive_ptr const & x, intrusive_ptr const & y) { return intrusive_ptr::compare(x, y) == 0; } class Refcount { public: Refcount() : reference_counter_(0) {} /* WARNING: virtual dtor here is essential, * since it's what allows us to invoke delete on a Refcount*, * for an object of some derived class type T. Otherwise clang * will use different addresses for Refcount-part and T-part of * such instance, which means pointer given to delete will not be * the same as pointer returned from new */ virtual ~Refcount() = default; uint32_t reference_counter() const { return reference_counter_.load(); } private: friend uint32_t intrusive_ptr_refcount(Refcount *); friend void intrusive_ptr_add_ref(Refcount *); friend void intrusive_ptr_release(Refcount *); private: std::atomic reference_counter_; }; /*Refcount*/ inline uint32_t intrusive_ptr_refcount(Refcount * x) { /* reporting accurately for diagnostics */ if (x) return x->reference_counter_.load(); else return 0; } /*intrusive_ptr_refcount*/ #ifdef XO_INTRUSIVE_PTR_ENABLE_LOGGING extern void intrusive_ptr_set_debug(bool x); extern void intrusive_ptr_log_ctor(std::string_view const & self_type, void * this_ptr, Refcount * x); /* here actor short for 'aliasing ctor' */ extern void intrusive_ptr_log_actor(std::string_view const & self_type, void * this_ptr, Refcount * x); extern void intrusive_ptr_log_cctor(std::string_view const & self_type, void * this_ptr, Refcount * x); extern void intrusive_ptr_log_mctor(std::string_view const & self_type, void *this_ptr, Refcount * x); extern void intrusive_ptr_log_dtor(std::string_view const & self_type, void * this_ptr, Refcount * x); extern void intrusive_ptr_log_assign(std::string_view const & self_type, void * this_ptr, Refcount * x); extern void intrusive_ptr_log_massign(std::string_view const & self_type, void *this_ptr, Refcount * x); #endif extern void intrusive_ptr_add_ref(Refcount * x); extern void intrusive_ptr_release(Refcount * x); template inline std::ostream & operator<<(std::ostream & os, intrusive_ptr const & x) { if(x.get()) { os << *(x.get()); } else { os << "() << ">"; } return os; } /*operator<<*/ /** Wrap a (presumably non-reference-counted) class T so that it has a refcount. **/ template class RefcountWrapper : public Refcount, public T { public: template RefcountWrapper(Args... args) : Refcount(), T(std::forward(args...)) {} }; /* borrow a reference-counted pointer to pass down the stack * 1. borrowed pointer intended to replace: * a. code like * foo(rp x), * passing rp by value requires increment/decrement pair, * which is superfluous given that caller holds reference throughout * b. code like * foo(rp const & x) * passing rp by reference requires double-indirection in called * code * 2. borrowed pointer does not check/maintain reference count. * it should never be stored in a struct; intended strictly * to be passed down stack * 3. just the same, want to be able to copy the borrowed pointer, * to avoid double-indirection * 4. also can promote borrowed pointer to full reference-counted * whenever desired */ template class Borrow { public: Borrow() = default; template Borrow(rp const & x) : ptr_(x.get()) {} template Borrow(S * x) : ptr_(x) {} Borrow(Borrow const & x) = default; /* convert from another borrow, if it has compatible pointer type */ template Borrow(Borrow const & x) : ptr_(x.get()) {} /* dynamic cast from a pointer to an object of some convertible type */ template static Borrow from(Borrow x) { return Borrow(dynamic_cast(x.get())); } /*from*/ /* promote from native pointer */ static Borrow from_native(T * x) { return Borrow(x); } /*from_native*/ T * get() const { return ptr_; } rp promote() const { return rp(ptr_); } T & operator*() const { return *ptr_; } T * operator->() const { return ptr_; } operator bool() const { return ptr_ != nullptr; } static int32_t compare(Borrow const & x, Borrow const & y) { return ptrdiff_t(x.get() - y.get()); } /*compare*/ static int32_t compare(rp const & x, Borrow const & y) { return ptrdiff_t(x.get() - y.get()); } /*compare*/ template Borrow & operator=(const Borrow & x) { ptr_ = x.get(); return *this; } private: T * ptr_ = nullptr; }; /*Borrow*/ template inline bool operator==(Borrow x, Borrow y) { return Borrow::compare(x, y) == 0; } template inline bool operator==(rp const & x, Borrow y) { return Borrow::compare(x, y) == 0; } template using brw = Borrow; template Borrow intrusive_ptr::borrow() const { return Borrow(*this); } /*borrow*/ } /*namespace ref*/ } /*namespace xo*/ /* end Refcounted.hpp */