2
\$\begingroup\$

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";
 }
asked Nov 9, 2019 at 23:44
\$\endgroup\$
1

0

Know someone who can answer? Share a link to this question via email, Twitter, or Facebook.

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.