blob: ed2b31bd6c73b8c3dd09b4b06d8468111b511179 [file] [log] [blame]
/*
* Copyright 2021 Rive
*/
#ifndef _RIVE_REFCNT_HPP_
#define _RIVE_REFCNT_HPP_
#include "rive/rive_types.hpp"
#include <atomic>
#include <cstddef>
#include <type_traits>
#include <utility>
/*
* RefCnt : Threadsafe shared pointer baseclass.
*
* The reference count is set to one in the constructor, and goes up on every call to ref(), and
* down on every call to unref(). When a call to unref() brings the counter to 0, the object is
* casted to class "const T*" and deleted. Usage:
*
* class MyClass : public RefCnt<MyClass>
*
* rcp : template wrapper for subclasses of RefCnt, to manage assignment and parameter passing
* to safely keep track of shared ownership.
*
* Both of these inspired by Skia's SkRefCnt and sk_sp
*/
namespace rive
{
template <typename T> class RefCnt
{
public:
RefCnt() : m_refcnt(1) {}
~RefCnt() { assert(this->debugging_refcnt() == 1); }
void ref() const { (void)m_refcnt.fetch_add(+1, std::memory_order_relaxed); }
void unref() const
{
if (1 == m_refcnt.fetch_add(-1, std::memory_order_acq_rel))
{
#ifndef NDEBUG
// we restore the "1" in debug builds just to make our destructor happy
(void)m_refcnt.fetch_add(+1, std::memory_order_relaxed);
#endif
delete static_cast<const T*>(this);
}
}
// not reliable in actual threaded scenarios, but useful (perhaps) for debugging
int32_t debugging_refcnt() const { return m_refcnt.load(std::memory_order_relaxed); }
private:
// mutable, so can be changed even on a const object
mutable std::atomic<int32_t> m_refcnt;
RefCnt(RefCnt&&) = delete;
RefCnt(const RefCnt&) = delete;
RefCnt& operator=(RefCnt&&) = delete;
RefCnt& operator=(const RefCnt&) = delete;
};
template <typename T> static inline T* safe_ref(T* obj)
{
if (obj)
{
obj->ref();
}
return obj;
}
template <typename T> static inline void safe_unref(T* obj)
{
if (obj)
{
obj->unref();
}
}
// rcp : smart point template for holding subclasses of RefCnt
template <typename T> class rcp
{
public:
constexpr rcp() : m_ptr(nullptr) {}
constexpr rcp(std::nullptr_t) : m_ptr(nullptr) {}
explicit rcp(T* ptr) : m_ptr(ptr) {}
rcp(const rcp<T>& other) : m_ptr(safe_ref(other.get())) {}
rcp(rcp<T>&& other) : m_ptr(other.release()) {}
template <typename U,
typename = typename std::enable_if<std::is_convertible<U*, T*>::value>::type>
rcp(const rcp<U>& other) : m_ptr(safe_ref(other.get()))
{}
template <typename U,
typename = typename std::enable_if<std::is_convertible<U*, T*>::value>::type>
rcp(rcp<U>&& other) : m_ptr(other.release())
{}
/**
* Calls unref() on the underlying object pointer.
*/
~rcp() { safe_unref(m_ptr); }
rcp<T>& operator=(std::nullptr_t)
{
this->reset();
return *this;
}
rcp<T>& operator=(const rcp<T>& other)
{
if (this != &other)
{
this->reset(safe_ref(other.get()));
}
return *this;
}
// move assignment operator
rcp<T>& operator=(rcp<T>&& other)
{
this->reset(other.release());
return *this;
}
T& operator*() const
{
assert(this->get() != nullptr);
return *this->get();
}
explicit operator bool() const { return this->get() != nullptr; }
T* get() const { return m_ptr; }
T* operator->() const { return m_ptr; }
// Unrefs the current pointer, and accepts the new pointer, but
// DOES NOT increment ownership of the new pointer.
void reset(T* ptr = nullptr)
{
// Calling m_ptr->unref() may call this->~() or this->reset(T*).
// http://wg21.cmeerw.net/lwg/issue998
// http://wg21.cmeerw.net/lwg/issue2262
T* oldPtr = m_ptr;
m_ptr = ptr;
safe_unref(oldPtr);
}
// This returns the bare point WITHOUT CHANGING ITS REFCNT, but removes it
// from this object, so the caller must manually manage its count.
T* release()
{
T* ptr = m_ptr;
m_ptr = nullptr;
return ptr;
}
void swap(rcp<T>& other) { std::swap(m_ptr, other.m_ptr); }
private:
T* m_ptr;
};
template <typename T> inline void swap(rcp<T>& a, rcp<T>& b) { a.swap(b); }
template <typename T, typename... Args> rcp<T> inline make_rcp(Args&&... args)
{
return rcp<T>(new T(std::forward<Args>(args)...));
}
template <typename T> rcp<T> inline ref_rcp(T* ptr) { return rcp<T>(safe_ref(ptr)); }
template <typename U,
typename T,
typename = typename std::enable_if<std::is_convertible<U*, T*>::value>::type>
rcp<U> static_rcp_cast(rcp<T> ptr)
{
return rcp<U>(static_cast<U*>(ptr.release()));
}
// == variants
template <typename T> inline bool operator==(const rcp<T>& a, std::nullptr_t) { return !a; }
template <typename T> inline bool operator==(std::nullptr_t, const rcp<T>& b) { return !b; }
template <typename T, typename U> inline bool operator==(const rcp<T>& a, const rcp<U>& b)
{
return a.get() == b.get();
}
// != variants
template <typename T> inline bool operator!=(const rcp<T>& a, std::nullptr_t)
{
return static_cast<bool>(a);
}
template <typename T> inline bool operator!=(std::nullptr_t, const rcp<T>& b)
{
return static_cast<bool>(b);
}
template <typename T, typename U> inline bool operator!=(const rcp<T>& a, const rcp<U>& b)
{
return a.get() != b.get();
}
} // namespace rive
#endif