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";
    }
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.