0

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?

Kischy
  • 51
  • 1
  • 8
  • 2
    It will not create a runtime error, it will exhibit undefined behavior which *might* result in a runtime error – Curious Jun 22 '17 at 08:47
  • 2
    check out the [visitor pattern](https://en.wikipedia.org/wiki/Visitor_pattern) – nefas Jun 22 '17 at 08:47
  • 1
    It completely depends on what you really want to do... "Accessing derived class attributes through base class." can arise in many situations, many of which can be directly avoided by good design... So without more information about you real goal, it will be hard to provide good advice... – Holt Jun 22 '17 at 08:49
  • 2
    depending on what you want to do, the most straightforward solution is probably just virtual functions – sp2danny Jun 22 '17 at 08:54
  • @nefas: I think you are right. The visitor pattern might be the ideal way to deal with this problem. Can you provide an example? I can't wrap my head around it. – Kischy Jun 22 '17 at 16:10

5 Answers5

2

Yes, you can do that without any casting:

class Base
{
    public:
        virtual ~Base() {}
        virtual int getSomeVar () = 0;
};

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

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

int main()
{
//    Base    B = Base(); // will not compile, therefore you're safe
    Derived D = Derived();

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

    std::cout << PointerToDerived->getSomeVar() << "\n"; // no cast needed

    return 0;

}

If you'd also like to be able to construct the Base class, you can just provide a "default" implementation of the method, instead of having it pure virtual.

(also, in this particular case, the method should be const)

EmDroid
  • 5,918
  • 18
  • 18
  • I was drafting the same answer – CocoCrisp Jun 22 '17 at 08:56
  • You have to be careful with this solution because it's tempting to do a bad design like adding a ``Derived2`` class with a ``string somevar2`` member and add a ``getSomevar2`` in ``Base`` even if it makes no sense for ``Derived`` to have ``getSomeVar2`` – nefas Jun 22 '17 at 09:49
2

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

nefas
  • 1,120
  • 7
  • 16
  • Thank you. What does the function call `bar(d.someVar)` do in this case? – Kischy Jun 23 '17 at 09:18
  • You can replace it by what you want. It was just to show that you can use a member function or variable that is not the class hierarchy. – nefas Jun 23 '17 at 09:23
  • Okay, I think I got it. I could just make the function `virtual int visit(Visitor) = 0;` with return type `int` and then change the implementation to `visit(Derived& d) {return d.getSomeVar;}`. Every other class that is derived from `Base` gets there own implementation of the `visit` function, while I add the overload in the `class visitor`. Thank you kindly. – Kischy Jun 23 '17 at 09:30
  • Or of course I could make it, like you pointed out, whatever I want. – Kischy Jun 23 '17 at 09:35
  • I've added a live example. You can make ``visit`` return an ``int`` but you can't make ``Base::visit`` return a type different than ``Derived::visit`` because it will not cause overriding. – nefas Jun 23 '17 at 09:44
  • Would there be something wrong with changing the function `void Base::visit(Visitor &)` to `int Base::visit(Visitor &)`? – Kischy Jun 23 '17 at 09:57
  • That's what I was saying (sorry if I was not clear). What I was saying is that you can't, for example, have ``void Base::visit(Visitor&)`` with `int Derived::visit(Visitor&)`` and `std::string Derived2::visit(Visitor&)``. Every ``visit`` must return the same type. – nefas Jun 23 '17 at 10:13
  • No worries. Thank you very much for your help. – Kischy Jun 23 '17 at 10:15
1

If I understand you correctly, you want to call a function of the derived class on the base class. That is not well defined.

Instead you can declare a virtual function in the base class, that will be overridden by a derived class. See Why do we need Virtual Functions in C++? and Wikipedia: Virtual Function for more details.

HeavensInc
  • 78
  • 6
0

There's a nice 'functional' reusable trick you can apply here:

template<typename Interface, typename Class, typename Function>
void with(Class * anObject, Function f) {
   if (auto * i = dynamic_cast<Interface*>(anObject))
     f(*i);
}

Usage:

with<Derived>(PointerToBase, [](auto &derived) {
  std::cout << derived.getSomeVar() << "\n"; // wont be called
}
with<Derived>(PointerToDerived, [](auto &derived) {
  std::cout << derived.getSomeVar() << "\n"; // will be called
}

Example at http://cpp.sh/9vzis

xtofl
  • 40,723
  • 12
  • 105
  • 192
0

Avoid runtime error.

Derived* derived = dynamic_cast<Derived*>(PointerToBase);
if (derived != NULL) {
    std::cout << ->getSomeVar() << "\n"; 
}

Certainly, you can't cast Base to Derived.

Jcppython
  • 179
  • 12