This is a lightweight version of std::shared_ptr<T>
called res_ptr<T>
This post is a continuation of: A lightweight version of std::shared_ptr<T>
Revisited the code due to review from @TobySpeight and @MartinYork. Fixed errors and implemented full support for all types of data.
std::shared_ptr<T>
stores a whole control block for managing reference counters (2 in total for weak_ptr
support) and a pointer to deleter so it can deal with casting between classes without virtual destructor. And most probably it stores it else where via additional allocation. Honestly, I am not too sure on all details of its implementation and I might be wrong on some points.
res_ptr<T>
stores the reference counter together with the data, it is achieved by wrapping T inside a class. A certain downside of it is that res_ptr<T>
cannot take ownership over a pointer to T
unless T
inherits from the reference counter class resource
.
In case T
inherits from class resource
then no wrapping occurs and you can freely cast res_ptr<T>
to res_ptr<U>
whenever both T
inherits from U
and U
inherits from resource
. It is left to the user to decide how exactly T
and U
derive from resource
, whether to use virtual
inheritance to deal with diamond problem or not. Also you can cast any res_ptr<T>
to res_ptr<resource>
and backwards is also possible via dynamic casting (or some unsafe operations).
Also implemented support for casting from std::unique_ptr<res_version<T>>
. It is possible to cast to std::unique_ptr<res_version<T>>
via function release
that relinquishes ownership over the data without reallocating it even if reference counter reaches 0.
res_version<T>
is an alias for the container class that res_ptr<T>
uses for storing T
. If T
inherits from resource
then res_version<T>
is simply T
.
make_resource<T>
is a helper function for instantiating res_ptr<T>
.
res_ptr
doesn't support vector versions like std::unique_ptr<T[]>
. And I haven't tested for unions.
Due to an initialization of the std::atomic
inside resource
it is not fully portable currently. Sorry for the issue. If there are any other issues with portability please let me know.
Here is a full working version of the code together with a test:
#include <iostream>
#include <atomic>
#include <cstddef>
#include <type_traits>
#include <utility>
class resource
{
public:
virtual ~resource() = default;
resource() = default;
// moving / copying does not alter the reference counter
resource(resource&&) noexcept : resource() {};
resource(const resource&) noexcept : resource() {};
resource& operator = (resource&&) noexcept {return *this;};
resource& operator = (const resource&) noexcept {return *this;};
void add_ref() const noexcept
{
m_refcount.fetch_add(1ull, std::memory_order_relaxed);
}
std::size_t reduce_ref() const noexcept
{
return m_refcount.fetch_sub(1ull, std::memory_order_relaxed)-1;
}
std::size_t count() const noexcept
{
return m_refcount.load(std::memory_order_relaxed);
}
private:
mutable std::atomic<std::size_t> m_refcount = 0;
};
template<typename T, typename Enable = void>
class res_version_ref;
template<typename T>
class res_version_ref<T, typename std::enable_if_t<std::is_base_of_v<resource, T>>>
{
public:
using type = T;
};
template<typename T>
class res_inheritance :
public T, public resource
{
public:
template<typename... Args>
res_inheritance(Args&&... args):
T(std::forward<Args>(args)...)
{};
};
template<typename T>
class res_version_ref<T, typename std::enable_if_t<!std::is_base_of_v<resource, T> && std::is_class_v<T>>>
{
public:
using type = res_inheritance<T>;
};
template<typename T, typename Enable = void>
class res_container;
template<typename T>
class res_container<T, typename std::enable_if_t<!std::is_abstract_v<T>>>:
public resource
{
public:
template<typename... Args>
res_container(Args&&... args):
m_data(std::forward<Args>(args)...)
{};
T m_data;
};
template<typename T>
class res_version_ref<T, typename std::enable_if_t<!std::is_class_v<T>>>
{
public:
using type = res_container<T>;
};
template<typename T>
using res_version = typename res_version_ref<T>::type;
template<typename T>
inline T* GetDataPtr(res_container<T>* ptr)
{
return &ptr->m_data;
}
template<typename T>
inline T* GetDataPtr(res_inheritance<T>* ptr)
{
return static_cast<T*>(ptr);
}
template<typename T>
inline T* GetDataPtr(T* ptr)
{
return ptr;
}
template<typename T>
class res_ptr
{
public:
template<typename U>
friend class res_ptr;
constexpr res_ptr() noexcept = default;
constexpr res_ptr(std::nullptr_t) noexcept {};
template<typename U, std::enable_if_t<std::is_base_of_v<res_version<T>, U>, int> = 0>
explicit res_ptr( U* ptr) noexcept :
m_ptr(static_cast<res_version<T>*>(ptr))
{
if(m_ptr) m_ptr->add_ref();
};
template<typename U, std::enable_if_t<std::is_base_of_v<res_version<T>, U>, int> = 0>
explicit res_ptr( std::unique_ptr<U> ptr) noexcept :
m_ptr(static_cast<res_version<T>*>(ptr.release()))
{
if(m_ptr) m_ptr->add_ref();
};
~res_ptr()
{
clear();
}
// copy ctor
res_ptr( const res_ptr& ptr) noexcept :
m_ptr(ptr.get())
{
if (m_ptr) m_ptr->add_ref();
};
// copy ctor cast
template<typename U, std::enable_if_t<std::is_base_of_v<res_version<T>, res_version<U>> && !std::is_same_v<res_version<T>, res_version<U>>,int> = 0>
res_ptr( const res_ptr<U> & ptr) noexcept :
m_ptr(static_cast<res_version<T>*>(ptr.m_ptr))
{
if (m_ptr) m_ptr->add_ref();
};
// move ctor
res_ptr( res_ptr&& ptr) noexcept :
m_ptr(std::exchange(ptr.m_ptr, nullptr))
{};
// move ctor cast
template<typename U, std::enable_if_t<std::is_base_of_v<res_version<T>, res_version<U>> && !std::is_same_v<res_version<T>, res_version<U>>,int> = 0>
res_ptr( res_ptr<U>&& ptr) noexcept :
m_ptr(static_cast<res_version<T>*>(std::exchange(ptr.m_ptr, nullptr)))
{};
// copy
res_ptr& operator = ( const res_ptr& other) noexcept
{
if (this != &other)
{
clear();
m_ptr = other.m_ptr;
if (m_ptr) m_ptr->add_ref();
}
return *this;
}
// move
res_ptr& operator = ( res_ptr&& other) noexcept
{
if (this != &other)
{
clear();
m_ptr = std::exchange(other.m_ptr,nullptr);
}
return *this;
}
// copy cast
template<typename U, std::enable_if_t<std::is_base_of_v<res_version<T>, res_version<U>> && !std::is_same_v<res_version<T>, res_version<U>>, int> = 0>
res_ptr& operator = ( const res_ptr<U>& other) noexcept
{
clear();
m_ptr = static_cast<res_version<T>*>(other.m_ptr);
if (m_ptr) m_ptr->add_ref();
return *this;
}
// move cast
template<typename U, std::enable_if_t<std::is_base_of_v<res_version<T>, res_version<U>> && !std::is_same_v<res_version<T>, res_version<U>>, int> = 0>
res_ptr& operator = ( res_ptr<U>&& other) noexcept
{
clear();
m_ptr = static_cast<res_version<T>*>(std::exchange(other.m_ptr,nullptr));
return *this;
}
// move cast unique_ptr
template<typename U, std::enable_if_t<std::is_base_of_v<res_version<T>, U>, int> = 0>
res_ptr& operator = ( std::unique_ptr<U> other) noexcept
{
clear();
m_ptr = static_cast<res_version<T>*>(other.release());
if(m_ptr) m_ptr->add_ref();
return *this;
}
T* operator -> () const noexcept
{
return GetDataPtr<T>(m_ptr);
}
T& operator * () const noexcept
{
return *GetDataPtr<T>(m_ptr);
}
T* get() const noexcept
{
return GetDataPtr<T>(m_ptr);
}
explicit operator bool() const noexcept
{
return m_ptr != nullptr;
}
void clear()
{
if (m_ptr && (m_ptr->reduce_ref() == 0ull))
{
delete m_ptr;
}
}
auto release() -> res_version<T>*
{
if (m_ptr)
{
m_ptr->reduce_ref();
}
return std::exchange(m_ptr, nullptr);
}
template<typename U>
bool operator == ( const res_ptr<U>& other) noexcept
{
return (void*)m_ptr == (void*)other.m_ptr;
}
template<typename U>
bool operator != ( const res_ptr<U>& other) noexcept
{
return (void*)m_ptr != (void*)other.m_ptr;
}
auto get_impl_ptr() const -> res_version<T>*
{
return m_ptr;
}
private:
res_version<T>* m_ptr = nullptr;
};
template<typename T, typename... Args>
res_ptr<T> make_resource(Args&& ... args)
{
return res_ptr<T>(new res_version<T>(std::forward<Args>(args)...));
}
template<typename PDerived, typename PBase>
res_ptr<PDerived> resource_dynamic_cast(const res_ptr<PBase>& uPtr) noexcept
{
res_version<PDerived>* ptr = dynamic_cast<res_version<PDerived>*>(uPtr.get_impl_ptr());
return res_ptr<PDerived>(ptr);
}
// testing code
class MyClassA:
public resource
{
public:
MyClassA() = default;
MyClassA(int A, int B):
m_dataA(A),
m_dataB(B)
{};
public:
int m_dataA;
int m_dataB;
};
class MyClassB:
public MyClassA
{
public:
MyClassB() = default;
MyClassB(int A, int B, int C):
MyClassA(A,B),
m_dataC(B)
{};
public:
int m_dataC;
};
class MyClassC:
public virtual resource
{
public:
MyClassC() = default;
MyClassC(int A):
m_dataC(A)
{};
public:
int m_dataC;
};
class MyClassD:
public virtual resource
{
public:
MyClassD() = default;
MyClassD(int A):
m_dataD(A)
{};
public:
int m_dataD;
};
class MyClassF:
public MyClassC, public MyClassD
{
public:
MyClassF() = default;
MyClassF(int A, int B, int C):
MyClassC(A),
MyClassD(B),
m_dataF(C)
{};
public:
int m_dataF;
};
int main()
{
res_version<int> A(5); A.add_ref();
res_version<std::string> B("abs"); B.add_ref();
res_version<MyClassA> C(2,3); C.add_ref();
std::cout << "A data " << *GetDataPtr(&A) << "\n";
std::cout << "B data "<< *GetDataPtr(&B) << "\n";
std::cout << "C data " << GetDataPtr(&C)->m_dataA << " " << GetDataPtr(&C)->m_dataB << "\n";
res_ptr<int> pA(&A);
res_ptr<std::string> pB(&B);
res_ptr<MyClassA> pC(&C);
res_ptr<resource> prA = pA;
res_ptr<resource> prB = pB;
res_ptr<resource> prC = pC;
std::cout << "A count " << A.count() << "\n";
std::cout << "B count " << B.count() << "\n";
std::cout << "C count " << C.count() << "\n";
res_ptr<int> pA2 = resource_dynamic_cast<int>(prA);
res_ptr<std::string> pB2 = resource_dynamic_cast<std::string>(prB);
res_ptr<MyClassA> pC2 = resource_dynamic_cast<MyClassA>(prC);
std::cout << "A count " << A.count() << "\n";
std::cout << "B count " << B.count() << "\n";
std::cout << "C count " << C.count() << "\n";
std::cout << "A2 data " << *pA2 << "\n";
std::cout << "B2 data " << *pB2 << "\n";
std::cout << "C2 data " << pC2->m_dataA << " " << pC2->m_dataB << "\n";
res_ptr<MyClassB> D = make_resource<MyClassB>(1,2,3);
res_ptr<MyClassA> DA = D;
std::cout << "DA data " << DA->m_dataA << " "<< DA->m_dataB << " count " << DA->count() << "\n";
std::cout << "DA == PA ? " << (int)(DA == pA) << "\n";
std::cout << "DA == D ? " << (int)(DA == D) << "\n";
res_ptr<MyClassB> D2 = resource_dynamic_cast<MyClassB>(DA);
std::cout << "D2 == D ? " << (int)(D2 == D) << "\n";
res_ptr<MyClassF> FF = make_resource<MyClassF>(1,2,3);
res_ptr<MyClassC> FC = FF;
res_ptr<MyClassD> FD = FF;
std::cout << "FC " << FC->m_dataC << "\n";
std::cout << "FD " << FD->m_dataD << "\n";
std::cout << "FF count " << FF->count() << "\n";
res_ptr<MyClassF> FF2 = resource_dynamic_cast<MyClassF>(FC);
std::cout << "FF2 " << FF2->m_dataF << "\n";
std::unique_ptr<res_version<int>> UP = std::make_unique<res_version<int>>(5);
std::unique_ptr<res_version<int>> UP2 = std::make_unique<res_version<int>>(8);
res_ptr<int> AA(std::move(UP));
AA = std::move(UP2);
std::cout << "AA " << *AA << "\n";
std::cout << "AA " << AA.get_impl_ptr()->count() << "\n";
res_ptr<int> AA2(std::make_unique<res_version<int>>(8));
std::cout << "AA2 " << *AA2 << "\n";
std::cout << "AA2 " << AA2.get_impl_ptr()->count() << "\n";
AA2 = AA2;
std::cout << "AA2 " << *AA2 << "\n";
std::cout << "AA2 " << AA2.get_impl_ptr()->count() << "\n";
AA2 = std::move(AA2);
std::cout << "AA2 " << *AA2 << "\n";
std::cout << "AA2 " << AA2.get_impl_ptr()->count() << "\n";
}
-
\$\begingroup\$ Good talk that touches on writing a custom shared_ptr: youtube.com/watch?v=Qq_WaiwzOtI \$\endgroup\$sudo rm -rf slash– sudo rm -rf slash2020年04月06日 05:50:03 +00:00Commented Apr 6, 2020 at 5:50