⬆️ ⬇️

Cast. Visual contrast static_cast from dynamic_cast

Good day. There are a lot of articles on the Internet about the difference of type-casting operators, but they didn’t add much understanding to me in this topic. I had to figure it out myself. I want to share with you my experience with a rather illustrative example.



The article is intended for those who want to realize type conversion in C ++.



So, let us have such hierarchy of inheritance:



#include <iostream> struct A{ A():a(0), b(0){} int a; int b; }; struct B : A{ B():g(0){} int g; }; struct D{ D():f(0){} float f; }; struct C : A, D{ C():d(0){} double d; }; 


The picture shows the inheritance hierarchy and the location of the data members of the heirs in memory

')

image



A small digression: why is type conversion important? Speaking of worker-peasant, when assigning an object of type X to an object of type Y, we must determine what value it will have after assigning an object of type X.



Let's start by using static_cast:



 int main(){ C* pC = new C; A* pA = pC; D* pD = static_cast<D*> (pC); std::cout << p << " " << pD << " " << pA << std::endl; return 0; } 


Why is this the effect on the output of pointer values ​​(the pointer value is the address where the variable lies)? The fact is that static_cast shifts the pointer.

Consider an example:



 D* pD = static_cast<D*> (pC); 


1. Type conversion occurs from C * to D *. The result of this is a pointer of type D * (let's call it tempD), which points (attention!) To that part in the object of class C, which is inherited from class D. The value of the pC itself does not change!



2. Now assign to the pD pointer the value of the tempD pointer (everything is fine, the types are the same)

The sensible question is: why do we actually need to move the pointer? In simple terms, the class D pointer is guided by the definition of class D. If there were no displacement, then changing the values ​​of the variables through the pointer D, we would change the variables of the object of class C that do not belong to the variables inherited from class D (if the pointer pD had the same meaning as pC, then when pD-> f was addressed, in reality we would work with the variable

but).



Subtotal: static_cast when working with a hierarchy of classes determines the values ​​of pointers so that the access to class variables through a pointer is correct.



Let's talk about the shortcomings of static_cast. Let's return to the same hierarchy of inheritance.



Consider this code:



 int main(){ C* pC = new C; A* pA = static_cast<A*>(pC); D* pD = static_cast<D*> (pC); B* pB = static_cast<B*> (pA); std::cout << &(pB->g) << " " << pD << " " << pA << std::endl; pB->g = 100; std::cout << pC->a << " " << pC->b << " " << pC->f << std::endl; return 0; } 


Why does pC-> f matter other than 0? Consider the code line by line:



  1. In the heap, memory is allocated for type C pointer.
  2. There is a step up conversion. The pA pointer has the same meaning as pC.
  3. There is a step up conversion. The pointer pD has the value, which is the ADDRESS of the variable f, in a class C object pointed to by the pC pointer.
  4. There is a down conversion. The pB pointer has the same meaning as the pA pointer.


Where is the danger? The fact is that in this embodiment, the pB pointer really believed that the object pointed to by pA was an object of type B. When converted, static_cast verifies that such a hierarchy really takes place (that is, that class B is a successor of class A), but it does not check that the object pointed to by pA is indeed an object of type B.



The danger itself:



image



Now if we want to write to the variable g through the pointer pB (after all, pB is completely sure that it points to an object of type B), we actually write the data to the variable f inherited from class D. And the pointer pD will interpret the information written to the variable f, like float, as we see when outputting via cout.



How to solve this problem?

To do this, use dynamic_cast, which checks not only the validity of the class hierarchy, but also the fact that the pointer actually points to an object of the type we want to lead to.



In order for such a check to be possible, you should add virtuality to the classes (dynamic_cast uses virtual function tables to do the check).



Demonstration of problem solving, with the same class hierarchy:



 int main(){ C* pC = new C; A* pA = pC; if(D* pD = dynamic_cast<D*> (pC)) std::cout << " OK " << std::endl; else std::cout << " not OK " << std::endl; if(B* pB = dynamic_cast<B*> (pA)) std::cout << " OK " << std::endl; else std::cout << " not OK " << std::endl; return 0; } 


I propose to run the code and make sure that the operation



 B* pB = dynamic_cast<B*> (pA) 


will not work (because pA points to an object of type C, which is what checked dynamic_cast and rendered its verdict).



I do not cite any links, the source is personal experience.



Thanks to all!

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



All Articles