c++inheritanceshared-ptrsmart-pointers

How do I cast a shared_ptr from one parent class to another parent class?


I am in the process of re-working my game engine to use smart-pointers. I have an Object class which everything inherits from. I have a GameObject which is renderable so it inherits from IRenderable (a class that defines a pure virtual render function) and does not inherit from Object. I have a RenderSystem which is supposed to hold a shared_ptr to all IRenderable in the scene.

The problem that I have is how do I cast my shared_ptr<GameObject> to an IRenderable for the RenderSystem?

Ideas that I have tried:

This is completely do-able with raw pointers and as such, I feel like there is some way to achieve the same result with smart pointers

Example:

// Object.h
class Object : public enable_shared_from_this<Object> { ... }
// GameObject.h
class GameObject : public Object { ... }
// MeshRenderer.h
class MeshRenderer : public GameObject, IRenderable { 
public:
    void initialize()
    {
         // Not able to cast Object to IRenderable
         RenderSystem::instance().addRenderable(getShared());
        
         // AND

         // Not able to cast Object to IRenderable
         
RenderSystem::instance().addRenderable(std::static_pointer_cast<IRenderable>(getShared()));
    }
}
// RenderSystem.h
class RenderSystem 
{
    std::list<std::shared_ptr<IRenderable>> m_renderables;
 public:
    void addRenderable(std::shared_ptr<IRenderable> ptr)
    {
       m_renderables.push_back(ptr);
    }
}

// main.cpp
...
auto meshRenderer = std::shared_ptr<MeshRenderer>();
...

Solution

  • Like this:

    #include <memory>
    
    // everything is an Object - yuk, but ok, if you wish...
    struct Object : std::enable_shared_from_this<Object>
    {
    };
    
    struct GameObject : Object
    {
    
    };
    
    struct IRenderable
    {
        virtual void render() {};
    };
    
    struct RederableGameObject : GameObject, IRenderable
    {
        auto as_shared_renderable() -> std::shared_ptr<IRenderable>
        {
            // builds a new shared pointer to IRenderable which
            // uses the same lifetime control block as me
            return std::shared_ptr<IRenderable>
            {
                this->shared_from_this(),   // where to get the control block
                this                        // what to point to
            };
        }
    };
    

    Documentation:

    See constructor number (8)

    http://en.cppreference.com/w/cpp/memory/shared_ptr/shared_ptr

    Update:

    Here is a starting point for a free function to fetch a correct shared_pointer from any object provided it is ultimately publicly derived from std::enable_shared_from_this

    #include <memory>
    #include <type_traits>
    
    namespace notstd
    {
        // stuff that I think *should be* std
    
        using namespace std;
    
        // a trait to determine whether class T is derived from template
        // Tmpl<...>
    
        template <typename T, template <class...> class Tmpl>
        struct is_derived_from_template_impl
        {
            static std::false_type test(...);
    
            template <typename...Us>
            static std::true_type test(Tmpl<Us...> const &);
    
            using result = decltype(test(std::declval<T>()));
        };
    
        template <typename T, template <class...> class Tmpl>
        using is_derived_from_template = typename is_derived_from_template_impl<T, Tmpl>::result;
    
        template <typename T, template <class...> class Tmpl>
        constexpr auto is_derived_from_template_v = is_derived_from_template<T, Tmpl>::value;
    
    
        // free function version of shared_from_this
    
        template<class T> 
        auto shared_from(enable_shared_from_this<T>* p) 
        -> std::shared_ptr<T>
        {
            return p->shared_from_this();
        }
    
        // specific shared_ptr construction from type T
    
        template<class T> 
        auto shared_from(T*p) 
        -> enable_if_t
        <
            is_derived_from_template_v
            <
                T, 
                enable_shared_from_this
            >, 
            std::shared_ptr<T>
        >
        {
            return std::shared_ptr<T>(p->shared_from_this(), p);
        }
    
    }
    
    // everything is an Object - yuk, but ok, if you wish...
    struct Object : std::enable_shared_from_this<Object>
    {
    };
    
    struct GameObject : Object
    {
    
    };
    
    struct IRenderable
    {
        virtual void render() {};
    };
    
    extern int emit(const char* str);
    
    struct RederableGameObject : GameObject, IRenderable
    {
        auto as_shared_renderable() -> std::shared_ptr<RederableGameObject>
        {
            return notstd::shared_from(this);
        }
    
        auto as_shared_renderable() const -> std::shared_ptr<const RederableGameObject>
        {
            return notstd::shared_from(this);
        }
    
        void e() const {
            emit("const");
        }
        void e()  {
            emit("mutable");
        }
    
    
    };
    
    
    
    int main()
    {
    
        auto rgo = std::make_shared<RederableGameObject>();
    
        // prove it works
        auto p1 = rgo->as_shared_renderable();
    
        // prove it works with a const object also
        auto p2 = static_cast<const RederableGameObject&>(*rgo).as_shared_renderable();
    
        p1->e();
        p2->e();
    }