Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Difference in usage of pointer downcast & upcast?

I am wondering what really happen in pointer casting when we are using downcast and upcast. I have 2 question. First 2 of them is the comment. Q3 is in the end.

#include<iostream>
using namespace std;
class A{
  public:
    virtual void  f()
    {
      cout<<"A"<<endl;
    }
};
class B: public A{
  public:
     virtual void   f()
    {
      cout<<"B"<<endl;
    }
};
int main()
{
  A* pa =new A();
  B* pb =new B();
  A* paUpcast= new B();
  /*
    Q1:
    Is the line above equivalent to the following?
    A* paUpcast = (A*) B;
  */
  B* pbDowncast=(B*)paUpcast;
  /*
    Q2:Why we cannot use the following 2 code;
    B* pbDowncast=new A();
    B* pbDowncast = (B*) pa;
  */
  pa->f();
  pb->f();
  paUpcast->f();
  pbDowncast->f();


  return 1;
}

Q3: I am trying to summary a rule to infer what will happene if we use the virtual function and pointer cast together, but I just cannot figure it out.

Originally, I think virtual function will lead us to where the pointer really pointed out. Therefore, when we type

A* paUpcast= new B();
paUpcast->f();

the second line will display "B" if A.f() is virtual function, because paUpcast is actually point to B object

However, when we type

B* pbDowncast=(B*)pa;
pbDowncast->f();

and it will display "A" instead of "B", which make the contradiction happen.

Can anyone explain or show me some tip? Thanks alot

like image 234
StevenR Avatar asked Oct 18 '12 05:10

StevenR


People also ask

Where is Upcasting and downcasting used?

In Java, we rarely use Upcasting. We use it when we need to develop a code that deals with only the parent class. Downcasting is used when we need to develop a code that accesses behaviors of the child class.

Why downcasting is not safe in C++?

Downcasting is not allowed without an explicit type cast. The reason for this restriction is that the is-a relationship is not, in most of the cases, symmetric. A derived class could add new data members, and the class member functions that used these data members wouldn't apply to the base class.

What is the use of Upcasting and downcasting in C++?

The Downcasting is an opposite process to the upcasting, which converts the base class's pointer or reference to the derived class's pointer or reference. It manually cast the base class's object to the derived class's object, so we must specify the explicit typecast.

Is used for downcasting base class pointers?

A dynamic cast expression is used to cast a base class pointer to a derived class pointer. This is referred to as downcasting.


2 Answers

I would try to explain how i understand it. the tip that helps me is to think about lego pieces.

In your case, we have two lego pieces, one named A and another named B... but just imagine that the B piece is a piece formed by attaching two pieces, one of the pieces is the same type of A:

 A     B
+-+  +-+-+
|a|  |a|b|
+-+  +-+-+

Then, you use pointers to reffer each of the lego pieces, but each piece has its own shape, so, just imagine:

A* pa =new A();
B* pb =new B();
A* paUpcast= new B();

A *pa -->       +-+   new A()
                |a|
                +-+
B* pb -->       +-+-+ new B()
                |a|b|
                +-+-+
A* paUpcast --> +-+-+ new B()
                |a|b|
                +-+-+

Note that the paUpcast pointer is a pointer of type A but holding a piece of type B, the B piece is different from the A one, as you can see is a piece slightly greater than its base.

This is the upcasting you're talking about, the base pointer is like a wildcard that can hold anything related downwards on the inheritance tree.

A* paUpcast= new B();

Is the line above equivalent to the following?

A* paUpcast = (A*) B;

Well, assuming that you really want to write this: A* paUpcast = (A*) new B(); yes, it is. You create a new instance of the B class and stores it into a pointer to A class, converting the new instance before assigning into the pointer doesn't changes the fact that it will be stored into a base class pointer anyway.

Why we cannot use the following 2 code;

B* pbDowncast=new A();

B* pbDowncast = (B*) A;

Remember the lego pieces. What happens while doing B* pbDowncast=new A()?:

B* pbDowncast --> +-+ new A()
                  |a|
                  +-+

Creating a new base clas instance and storing it into a pointer to derived class, you're trying to treat the base as derived, if you look closely the lego piece doesn't fit! the A piece lacks of the extra stuff necessary to be considered of the B kind; all this stuff "is stored" into the extra part of the lego piece, B = all the A stuff plus something more:

  B
+-+-----------------------------------+
|a|extra stuff that only B pieces have|
+-+-----------------------------------+

What would happen if you try to call a method that only the B class has? Having a B pointer you're allowed to call all the B methods, but the instance you created is from A kind that wouldn't have the B methods, it wasn't created with all this extra stuff.

However, when we type

B* pbDowncast=(B*)pa;

pbDowncast->f();

display "A" instead of "B", which make the contradiction happen.

It doesn't ressembles a contradiction to me, remembering the lego pieces, the pa pointer is pointing to a piece of type A:

A *pa --> +-+
          |a|
          +-+

This piece lacks of all of the B stuff, the fact is that lacks the f() method that prints B on the standard output... but it have a method f() that prints A on the output.

I hope it helps!

EDIT:

It seems that you also agree that it is inappropriate to use the downcast isn't it?

No, i don't agree. Downcasting isn't inappropiate at all, but it would be inappropiate depending on its use. Like all the C++ tools, the downcasting has an utility and scope of use; all the trickery that respect the good use would be appropiate.

What would be a good use of the downcasting tool then? IMHO anything that wouldn't break the code or the program flow, maintaining the code as readable as possible and (the most important for me) if the programmer knows what is he doing.

Downcasting taking a possible inheritance branch is a common practice after all:

A* paUpcast = new B();
static_cast<B*>(paUpcast)->f();

But it would be troublesome with a more complex inheritance tree:

#include<iostream>
using namespace std;
class A{
public:
    virtual void  f()
    {
        cout<<"A"<<endl;
    }
};
class B: public A{
public:
    virtual void   f()
    {
        cout<<"B"<<endl;
    }
};
class C: public A{
public:
    virtual void   f()
    {
        cout<<"C"<<endl;
    }
};

A* paUpcast = new C();
static_cast<B*>(paUpcast)->f(); // <--- OMG! C isn't B!

To deal with this, you can get use of the dynamic_cast

A* paUpcast = new C();

if (B* b = dynamic_cast<B*>(paUpcast))
{
    b->f();
}

if (C* c = dynamic_cast<C*>(paUpcast))
{
    c->f();
}

But dynamic_cast is well known by it's lack of performance, you can study some alternatives to dynamic_cast, like internal object identifiers or conversion operators but in order to stick to the question, the downcasting isn't bad at all if it is used correctly.

like image 53
PaperBirdMaster Avatar answered Sep 20 '22 22:09

PaperBirdMaster


Don't use C-style casts in C++! There is no reason to do so and it is obfuscating the meaning of what you are trying to do and, more importantly, it can yield interesting results if you don't cast what you think you are casting. In the situation above you can always use static_cast<T*>() instead, although all downcasts should probably better be dynamic_cast<T*>(), instead: The latter only succeeds if the cast is actually meant to work in which case it returns a pointer to the correct object, possibly adjusting the pointer value as needed (the pointer value may change in situations involving multiple inheritance). If dynamic_cast<T*>(x) fails, i.e., x isn't really a pointer to an object of type T (or derived), it return null.

Now, on to the question: The pointer casts you do only affect the pointer and don't affect the object. That is, the type of the pointed to object will never change. In scenario for question 1 you create a pointer to a derived type and assign it to a pointer to the base type. Since a derived type is-a base type this conversion is implicit but it equivalent to

A* paUpcast = static_cast<A*>(pb);

In the second scenario, you cast paUpcast to a B*. Since paUpcast was the result of converting a B* into an A* the conversion back to a B* works using static_cast<T*>(x): When casting pointers or reference using static_cast<>() you can reverse the effect of an implicit conversion. If you want to navigate a class hierarchy in any other way than reversing the effect of an implicit conversion you need to use dynamic_cast<>(). You could have used dynamic_cast<>() in this situation, too, but dynamic_cast<>() has some cost to.

Now, for the third scenario you have pa actually pointing to an A object which is not a B object. The compiler will allow you to cast pa to B* but you are lying to the compiler: pa is not the result of an implicit conversion of a B* to an A* and using static_cast<B*>(pa) (or (B*)pa which are in this case equivalent but C-style casts are not always equivalent to static_cast<>()s) results in undefined behavior: The compiler does whatever it feels it wants to do. Since the layout of a A and B object is similar, it ends up calling a virtual function of A but this is just one of many possible outcomes and there is no guarantees what happens. If you had used dyanmic_cast<B*>(pa) the result would have been a null pointer, indicating that the cast from pa to B* does not work.

like image 36
Dietmar Kühl Avatar answered Sep 21 '22 22:09

Dietmar Kühl