Jump to content
  • Advertisement
Sign in to follow this  
Juliean

Need advice on custom smart-ptr/handle implementation

This topic is 2154 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

Hello,

 

for a while I've just been passing around raw pointers from my resource systems, since all I ever did was add resources or clear them in between levels, but now that I've got an editor where I can delete resources any time, I'd like to have some sort of safer ownership.

 

Now, I already know smart pointers and have applied them at some instances, but I've also read that those can be slow, plus I e.g. don't really like the way you have to first lock an std::weak_ptr to access its internals. So since I'm already writing a game engine per se, I figured why not write such a thing myself. I really need only simply ownership rules anyway, like:

 

- Resources are explicitely owned by the resource cache.

- External access only needs to work through weak references, when the resources is removed from the cache all references are invalidated.

- Though, optional reference counting would be nice

- Reference counting should be implicitely (no AddRef and Release)

- Only validity-checks when needed (in the function I access the texture right after loading it without any exception, I know it is valid and there is no need to lock() or check whether it actually still exists)

 

So, I've written a couple of classes to assist me in this process, and I'd like to have some feedback, whether they'd fit my case, what I could improve/simplify, what else I could eventually need, etc...:

#pragma once

namespace acl
{
    namespace core
    {

        template<typename Type>
        class Owner;

        /// Handles the interaction between owner and handle
        /** This class is used to allow a handle to query for validity of its owner, even after the owner has
        *   been destroyed, and without needing to directly notify the handle. It uses reference counting to
        *   free its memory after the owner and all handles are removed. */
        template<typename Type>
        class Shared
        {
        public:

            Shared(Owner<Type>& owner) : m_pOwner(&owner),
                m_references(1)
            {
            }

            void AddRef(void)
            {
                m_references++;
            }

            void Release(void)
            {
                m_references--;
                if(!m_references)
                    delete this;
            }

            void SetOwner(Owner* pOwner)
            {
                m_pOwner = pOwner;
            }

            bool HasValidOwner(void) const
            {
                return m_pOwner != nullptr;
            }

            Type& GetObject(void)
            {
                return m_pOwner->m_object;
            }

            const Type& GetObject(void) const
            {
                return m_pOwner->m_object;
            }

        private:

            Shared(const Shared<Type>& shared) {};
            Shared(Shared<Type>&& shared) {};
            void operator=(const Shared<Type>& shared) {};
            void operator=(Shared<Type>&& shared) {};

            int m_references;
            Owner<Type>* m_pOwner;
        };

        /// Stores a safe, weak reference to an object
        /** This class acts as sort of a weak smart pointer. It automatically handles the lifetime and should
        *    always be stored by value, and checked for validity before accessing the object it referes to.
        *   A handle is typically created by an owner, but it can be default-initialized and copyied/moved
        *    from any other handle. The refered object can be directly accessed, though checking validity beforehands
        *    is recommended unless its asolutely safe the parent still exists. The raw pointer accessed this way may never
        *    be stored inside a class or as a global, in which case validity could not be queried and/or quaranted anymore.*/
        template<typename Type>
        class Handle final
        {
        public:

            Handle(void) : m_pShared(nullptr)
            {
            }

            Handle(Shared<Type>& shared) : m_pShared(&shared)
            {
                m_pShared->AddRef();
            }

            Handle(const Handle& handle) : m_pShared(handle.m_pShared)
            {
                m_pShared->AddRef();
            }

            Handle(Handle&& handle) : m_pShared(handle.m_pShared)
            {
                handle.m_pShared = nullptr;
            }

            Handle& operator=(const Handle& handle)
            {
                handle.m_pShared.AddRef();

                if(m_pShared)
                    m_pShared->Release();

                m_pShared = handle.m_pShared;

                return *this;
            }

            Handle& operator=(Handle&& handle)
            {
                if(m_pShared != handle.m_pShared)
                {
                    if(m_pShared)
                        m_pShared->Release();
                    m_pShared = handle.m_pShared;
                }
                
                return *this;
            }

            ~Handle(void)
            {
                if(m_pShared)
                    m_pShared->Release();
            }

            bool IsValid(void)
            {
                return m_pShared && m_pShared->HasValidOwner();
            }

            void Invalidate(void)
            {
                m_pShared->Release();
                m_pShared = nullptr;
            }

            operator Type&(void)
            {
                return m_pShared->GetObject();
            }

            operator const Type&(void) const
            {
                return m_pShared->GetObject();
            }

            operator Type*(void)
            {
                return &m_pShared->GetObject();
            }

            operator const Type*(void) const
            {
                return &m_pShared->GetObject();
            }

            Type* operator->(void)
            {
                return &m_pShared->GetObject();
            }

            const Type* operator->(void) const
            {
                return &m_pShared->GetObject();
            }

        private:

            Shared<Type>* m_pShared;
        };

        /// Manages a single, non-trivially movable ownership to an object
        /** This class creates an instance of the specified type directly on the stack when initialized.
        *    It also creates a shared instance to communicate with its handles. Ownership cannot be reassigned,
        *    though copy/move construction is possible, if the object supports a copy/move constructor.*/
        template<typename Type>
        class Owner final
        {
        public:

            template<typename... Args>
            Owner(Args&&... args) : m_object(args...)
            {
                m_pShared = new Shared<Type>(*this);
            }

            Owner(const Owner& owner) : m_object(owner.m_object),
            {
                m_pShared = new Shared<Type>(*this);
            }

            Owner(Owner&& owner) : m_object(std::move(owner.m_object)),
                m_pShared(owner.m_pShared)
            {
                m_pShared->SetOwner(this);
                owner.m_pShared = nullptr;
            }

            ~Owner(void)
            {
                m_pShared->SetOwner(nullptr);
                m_pShared->Release();
            }

            Handle<Type> CreateHandle(void)
            {
                return Handle<Type>(*m_pShared);
            }

        private:

            void operator=(const Owner& owner) {}
            void operator=(Owner&& owner) {}

            friend class Shared<Type>;

            Type m_object;
            Shared<Type>* m_pShared;
        };

    }
}

Usage would be like this:

std::vector<Owner<Texture*>> m_vOwner;
...
m_vOwner.emplace_back(new Texture(...));// somewhere in the resource loader

...


Handle<Texture*> texture = m_textureCache(15);
if(texture.IsValid())
    texture->GetPixel(...);
else
    log("Invalid texture");

auto anotherHandle = handle;
handle.Invalidate();

assert(anotherHandle.IsValid());

m_textureCache.Remove(15);

assert(!anotherHandle.IsValid());

So, any feedback? Could be anything from usability, performance, possible pitfalls you see... thanks!

Share this post


Link to post
Share on other sites
Advertisement
Sign in to follow this  

  • Advertisement
×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

GameDev.net is your game development community. Create an account for your GameDev Portfolio and participate in the largest developer community in the games industry.

Sign me up!