330 lines
12 KiB
C++
330 lines
12 KiB
C++
/* @file Refcounted.hpp */
|
|
|
|
#pragma once
|
|
|
|
#include "xo/indentlog/scope.hpp"
|
|
#include "xo/cxxutil/demangle.hpp"
|
|
|
|
//#include <boost/intrusive_ptr.hpp>
|
|
#include <atomic>
|
|
#include <cassert>
|
|
|
|
namespace xo {
|
|
namespace ref {
|
|
class Refcount;
|
|
|
|
template<typename T>
|
|
class Borrow;
|
|
|
|
/* originally used boost::instrusive_ptr<>.
|
|
* ran into a bug. probably mine, but implemented
|
|
* refcounting inline for debugging
|
|
*/
|
|
template<typename T>
|
|
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<typename S>
|
|
intrusive_ptr(intrusive_ptr<S> 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<typename Y>
|
|
intrusive_ptr(intrusive_ptr<Y> const & /*x*/, element_type * y) : ptr_{y} {
|
|
if (std::is_same<Y, element_type>()) {
|
|
#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<Y>()),
|
|
xtag("T", reflect::type_name<T>())));
|
|
}
|
|
} /*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<T> const & x,
|
|
intrusive_ptr<T> const & y) {
|
|
return ptrdiff_t(x.get() - y.get());
|
|
}
|
|
|
|
Borrow<T> borrow() const;
|
|
|
|
T * get() const { return ptr_; }
|
|
|
|
T * operator->() const { return ptr_; }
|
|
|
|
operator bool() const { return ptr_ != nullptr; }
|
|
|
|
intrusive_ptr<T> & operator=(intrusive_ptr<T> 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<T> & operator=(intrusive_ptr<T> && 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<intrusive_ptr<T>>();
|
|
|
|
private:
|
|
T * ptr_ = nullptr;
|
|
}; /*intrusive_ptr*/
|
|
|
|
template<typename T>
|
|
inline bool operator==(intrusive_ptr<T> const & x,
|
|
intrusive_ptr<T> const & y) { return intrusive_ptr<T>::compare(x, y) == 0; }
|
|
|
|
template<typename T>
|
|
using rp = intrusive_ptr<T>;
|
|
|
|
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<uint32_t> 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<typename T>
|
|
inline std::ostream &
|
|
operator<<(std::ostream & os, intrusive_ptr<T> const & x) {
|
|
if(x.get()) {
|
|
os << *(x.get());
|
|
} else {
|
|
os << "<nullptr " << reflect::type_name<T>() << ">";
|
|
}
|
|
return os;
|
|
} /*operator<<*/
|
|
|
|
/** Wrap a (presumably non-reference-counted) class T so that it has a refcount.
|
|
**/
|
|
template <typename T>
|
|
class RefcountWrapper : public Refcount, public T {
|
|
public:
|
|
template<typename... Args>
|
|
RefcountWrapper(Args... args) : Refcount(), T(std::forward<Args...>(args...)) {}
|
|
};
|
|
|
|
/* borrow a reference-counted pointer to pass down the stack
|
|
* 1. borrowed pointer intended to replace:
|
|
* a. code like
|
|
* foo(rp<T> x),
|
|
* passing rp<T> by value requires increment/decrement pair,
|
|
* which is superfluous given that caller holds reference throughout
|
|
* b. code like
|
|
* foo(rp<T> const & x)
|
|
* passing rp<T> 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<typename T>
|
|
class Borrow {
|
|
public:
|
|
Borrow() = default;
|
|
|
|
template <typename S>
|
|
Borrow(rp<S> const & x) : ptr_(x.get()) {}
|
|
|
|
template <typename S>
|
|
Borrow(S * x) : ptr_(x) {}
|
|
|
|
Borrow(Borrow const & x) = default;
|
|
|
|
/* convert from another borrow, if it has compatible pointer type */
|
|
template<typename S>
|
|
Borrow(Borrow<S> const & x) : ptr_(x.get()) {}
|
|
|
|
/* dynamic cast from a pointer to an object of some convertible type */
|
|
template<typename S>
|
|
static Borrow<T> from(Borrow<S> x) {
|
|
return Borrow(dynamic_cast<T *>(x.get()));
|
|
} /*from*/
|
|
|
|
/* promote from native pointer */
|
|
static Borrow<T> from_native(T * x) {
|
|
return Borrow(x);
|
|
} /*from_native*/
|
|
|
|
T * get() const { return ptr_; }
|
|
|
|
rp<T> promote() const { return rp<T>(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<T> const & x, Borrow const & y) {
|
|
return ptrdiff_t(x.get() - y.get());
|
|
} /*compare*/
|
|
|
|
template <typename S>
|
|
Borrow<T> & operator=(const Borrow<S> & x) {
|
|
ptr_ = x.get();
|
|
return *this;
|
|
}
|
|
|
|
private:
|
|
T * ptr_ = nullptr;
|
|
}; /*Borrow*/
|
|
|
|
template<typename T>
|
|
inline bool operator==(Borrow<T> x, Borrow<T> y) { return Borrow<T>::compare(x, y) == 0; }
|
|
|
|
template<typename T>
|
|
inline bool operator==(rp<T> const & x, Borrow<T> y) { return Borrow<T>::compare(x, y) == 0; }
|
|
|
|
template<typename T>
|
|
using brw = Borrow<T>;
|
|
|
|
template<typename T>
|
|
Borrow<T>
|
|
intrusive_ptr<T>::borrow() const {
|
|
return Borrow<T>(*this);
|
|
} /*borrow*/
|
|
|
|
} /*namespace ref*/
|
|
} /*namespace xo*/
|
|
|
|
/* end Refcounted.hpp */
|