C++ - upcasting and downcasting
Asked Answered
G

3

6

In my example:

At upcasting, shouldn't the second d.print() call print "base"?

Isn't it "d" derived object upcasted to a base class object?

And at downcasting, what advantages does it have?

Could you explain upcast and downcast in a practical way?

#include <iostream>
using namespace std;

class Base {
public:
    void print() { cout << "base" << endl; }
};

class Derived :public Base{
public:
    void print() { cout << "derived" << endl; }

};

void main()
{
    // Upcasting
    Base *pBase;
    Derived d;
    d.print();
    pBase = &d;
    d.print();

    // Downcasting
    Derived *pDerived;
    Base *b;
    pDerived = (Derived*)b;
}
Gilbertina answered 30/1, 2016 at 12:59 Comment(1)
Why do you think the pBase line should change the behaviour of d.print(); ? Did you mean to ask about pBase->print(); ?Timothea
I
19

Up-casting is implicit in C++, and is used a lot when you deal with virtual dispatching. In other words, you have a pointer to Base from which you can access the common interface of a whole hierarchy of classes, and the selection can be done at runtime. This assumes that your interface functions are marked virtual. Example:

Base* pBase; 
cin >> x; 
if(x == 0) // this is done at runtime, as we don't know x at compile time
    pBase = new Derived1;
else
    pBase = new Derived2;

pBase->draw(); // draw is a virtual member function

It is extremely useful in these situations in which the dispatch is done at runtime. Simply said, upcasting allows one to treat a derived class as a base class (via its common interface).

Down-casting is less useful, and IMO should be avoided whenever one can. In general is a sign of bad design, as one rarely needs to convert a Base object to a derived one. It can be done (and the result checked) via dynamic_cast, like

Base* pBase = new Derived; // OK, the dynamic type of pBase is Derived
Derived* pDerived = dynamic_cast<Derived*>(pBase);
if(pDerived) // always test  
{
    // success
}
else
{
    // fail to down-cast
}

This link provides a quite useful intro to the subject.

Ietta answered 30/1, 2016 at 13:10 Comment(9)
So the code lines : "pBase = new Derived1;" and "pBase = new Derived2;" represent actually upcasting ? If so , let's assume x is 0 and pBase will point to an object of "Derived1" class . What would be the difference if pBase pointer would be "Derived1 *pBase " ?Gilbertina
@Gilbertina It's useful whenever you don't know in advance the derived type. In that case you use the pointer to base to control the derived hierarchy.Ietta
Down-casting is bad design? That is an insane point to make! It's up-casting that's bad design since it leads to undefined behaviour that is unkown at compile-time and unpredictable at run-time. It's actually the down-casting example that provides a solid foundation of work.Bobbe
dynamic_cast<Derived> should be dynamic_cast<Derived *>.Jaala
@Bobbe I hope you realize that Base* p = new Derived is (implicit) upcasting, not downcasting. Upcasting is used way more than downcasting. Again, IMO, dynamic_cast should be used sparsely.Ietta
Imo, it's static_cast that needs to be used sparsely. dynamic_cast is used a lot and provides a safe way to convert from one type to another. When it comes to casting a base to a derived, it's there to use specific functions of that derived which otherwise would bloat or disrupt the base interface. Maybe you're looking at things too generically or viewing the design from a technical POV rather than philosophical one.Bobbe
@Bobbe I agree one should not static_cast on dynamic types. Upcasting is implicit though, it's just about controlling the interface of a class hierarchy via a base pointer. And this is important IMO.Ietta
By the way, the OP did not make use of virtual methods, which will not allow him to make use of virtual dispatching.Jaala
As an addendum, there are some situations where downcasting is a perfectly valid choice, rather than bad design. These are primarily cases where the actual type is known upon use (and typically intended to allow access to the full interface (if applicable), rather than only the shared portion), or hidden within the class itself. The former may come up when an object has to be passed through code that doesn't need to know its actual type between creation and consumption, and the latter allows the base to check compatibility before dispatching (e.g., for polymorphic comparison operators).Cristicristian
J
9

You need to use virtual methods in order to enable RTTI.

In your case, since you are using C++, you should rely on safer casting mechanisms. So, instead of (Derived*)b, you should be using dynamic_cast<Derived*>(b). This allows you to make sure that you are actually in possession of a pointer to an object to a base class (interface) that was obtained by casting up an object of type Derived. This page provides further explanations.

Jaala answered 30/1, 2016 at 13:6 Comment(2)
I am not downvoter (maybe I should..) but your answer is pretty good asnwer but not to this very question above - I guess this is the reason.Enthronement
Well, to my defence, the question wasn’t all that great either. Please let me know how you think I should improve my answer.Jaala
A
1

If you want to call print "Base", you should do it like this; here, we are upcasting a derived class object to this base class object, so, when we call pBase->print(), it goes to base class and calls it; but, if the print function is virtual, it will call the derived class, becoase it is overriding this function in the derived class.

void main(){
    // Upcasting
    Base *pBase;
    Derived d;
    d.print();
    pBase = &d;
    pBase->print();
}
Aubigny answered 29/5, 2023 at 17:23 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.