Search code examples
c++pointersradixdynamic-castderived

C++ How to access derived class member from base class pointer without using a static_cast or dynamic_cast?


I saw the following question and I asked myself if there is a better way to address this problem, so there is no need for a cast. Consider the following code:

#include <iostream>

class Base
{
    public:
        virtual ~Base() {}
};

class Derived : public Base
{
    protected:
        int someVar = 2;

    public:
        int getSomeVar () {return this->someVar;}   
};


int main()
{
    Base    B = Base(); 
    Derived D = Derived();

    Base *PointerToDerived  = &D;
    Base *PointerToBase     = &B;

    std::cout << dynamic_cast<Derived*>(PointerToDerived)->getSomeVar() << "\n"; //this will work
    std::cout << dynamic_cast<Derived*>(PointerToBase)->getSomeVar() << "\n"; //this will create a runtime error

    return 0;

}

Is there a better way to design this, so no cast is needed and runtime errors like this can be avoided?


Solution

  • The visitor pattern use double dispatch to allow you to use class-specific member function and member variable (opposed to member function/variable available through the hierarchy).

    To implement the visitor pattern, you need a visitor and a hierarchy of visited (in your example it's Base and the classes derived from Base). With your example it would give something like that:

    class Base
    {
        public:
            virtual ~Base() {}
            virtual void visit(Visitor) = 0;
    };
    
    class Derived : public Base
    {
        protected:
            int someVar = 2;
    
        public:
            int getSomeVar () {return this->someVar;}
            void visit(Visitor& v) {
                v.visit(this);
             }
    };
    class Visitor {
        public:
             void visit(Derived& d) {
                  bar(d.someVar);
             }
    };
    

    The idea behind the visitor is that this of Derived know it's real type while a polymorphic variable (a Base& or Base*) don't. OverridingBase::visit allow you to call the visit who will do the dispatch to the right Visitor::visit.

    If you don't override Base::visit, when calling it on a Base& (or Base*) it will call the function on a Base object (so this will be of type Base*). That's why in the example Base::visit is abstract, doing otherwise will only make error more likely to happen (like forgetting to override visit).

    When adding a new type in the Base hierarchy (for example a Derived2 class), you will need to add two functions: Derived2::visit and Visitor::visit(Derived2) (or Visitor::visit(Derived2&)).

    EDIT

    live example