Difference in using downcast and upcast pointer?

I am wondering what actually happens when casting a pointer, when we use downcast and upcast. I have 2 questions. The first 2 of them are comments. Q3 at 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 generalize the rule to conclude what happens if we use a virtual function and a pointer combined, but I just can't figure it out.

Initially, I think a virtual function will lead us to where the pointer really pointed. Therefore, when we introduce

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

the second line will display โ€œBโ€ if Af () is a virtual function, because paUpcast actually points to object B

However, when we introduce

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

and instead of โ€œBโ€, โ€œAโ€ is displayed, which leads to a contradiction.

Can someone explain or show me a hint? thanks a lot

+6
source share
2 answers

I will try to explain how I understand this. the clue that helps me is to think about the parts of the lego.

In your case, we have two lego parts, one of which is called A , and the other is called B ... but imagine that piece B is a piece formed by attaching two parts, one of the parts is the same type A :

  AB +-+ +-+-+ |a| |a|b| +-+ +-+-+ 

Then you use pointers to play each lego part, but each part has its own shape, so 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 , fragment B is different from A , as you can see a little more of its base.

This is the up level that you are talking about, the base pointer is like a wildcard that might contain anything linked down in the inheritance tree.

A * paUpcast = new B ();

Is the line above equivalent to the next?

A * paUpcast = (A *) B;

Well, if you really want to write this: A* paUpcast = (A*) new B(); Yes it is. You create a new instance of class B and save it in a pointer to class A , converting the new instance before assigning it to a pointer does not change the fact that it will be stored in the pointer of the base class in any case.

Why can't we use the following code:

B * pbDowncast = new A ();

B * pbDowncast = (B *) A;

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

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

By creating a new instance of the base class and storing it in a pointer to a derived class, you are trying to treat the database as a derivative, if you look closely at the lego part, it does not fit! in piece A no additional materials that must be considered in type B ; all this stuff is โ€œstoredโ€ in the extra part of the lego part, B = all the A stuff plus something more :

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

What happens if you try to call a method that only has class B ? With pointer B , you are allowed to call all methods of B , but the instance you created has the form A , which does not have methods B , it was not created with all these additional things.

However, when we introduce

B * pbDowncast = (B *) pa;

pbDowncast-> F ();

display "A" instead of "B", which causes a contradiction.

This does not contradict me, remembering the parts of lego, the pointer pa points to a piece of type A :

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

This piece is missing all the material B , the fact is that there is no method f() that prints B on standard output ... but it has a method f() that prints A on output.

Hope this helps!

EDIT:

It looks like you also agree that using downcast doesn't it?

No I do not agree. Downcasting is not at all inappropriate, but it will be inadequate depending on its use. Like all C ++ tools, downcasting has a utility and a scope; All tricks that respect good use will be approved.

What would be useful to use the downcasting tool? IMHO everything that would not break the code or the program flow, keeping the code as readable as possible and (most important for me) if the programmer knows what he is doing.

Downcasting with a possible inheritance branch is common practice:

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

But that 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 handle this, you can use 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 well known for its lack of performance , you can explore some alternatives to dynamic_cast , for example, internal object identifiers or conversion operators, but downcasting is not bad if it is used correctly to adhere to this question.

+8
source

Do not use C-style cast in C ++! There is no reason for this, and this confuses the meaning of what you are trying to do, and, more importantly, it can produce interesting results if you do not give up what you think you are producing. In the above situation, you can always use static_cast<T*>() instead, although it is probably best down dynamic_cast<T*>() be dynamic_cast<T*>() : the latter only succeeds if the actor actually needs to work , and in this case, it returns a pointer to the correct object, possibly adjusting the value of the pointer as necessary (the value of the pointer may change in situations involving multiple inheritance). If dynamic_cast<T*>(x) fails, i.e. x not a pointer to an object of type T (or derived), it returns null.

Now, to the question: The order of the pointer affects only the pointer and does not affect the object. That is, the type of object that the object points to will never change. In the script for question 1, you create a pointer to a derived type and assign it to a pointer to the base type. Since the derived type is the base type, this conversion is implicit, but it is equivalent

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

In the second scenario, you added paUpcast to B* . Since paUpcast was the result of converting a B* to A* , converting back to B* works using static_cast<T*>(x) : when specifying a cast or link using static_cast<>() you can undo the effect of implicit conversion, If you if you want to navigate the class hierarchy in some other way, except reversing the effect of the implicit conversion, you need to use dynamic_cast<>() . You could use dynamic_cast<>() in this situation dynamic_cast<>() , but dynamic_cast<>() has some cost.

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

+3
source

Source: https://habr.com/ru/post/927993/


All Articles