Cast. Visual difference between static_cast and dynamic_cast

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

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

So, let us have such a hierarchy of inheritance:

#include 
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 so important? In worker-peasant terms, when assigning an object of type X to an object of type X, we must determine what value an object of type X will have after assigning it.

Let's start by using static_cast:

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

Why is there such an effect when outputting pointer values ​​(the pointer value is the address at which the variable lies)? The fact is that static_cast produces a pointer shift.
Consider an example:

D* pD = static_cast (pC); 

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

2. Now we assign to the pD pointer the value of the tempD pointer (everything is fine, the types are the same)
A reasonable question: why actually need to move the pointer? In simple terms, the pointer to the class D * is guided by the definition of the class D. If there were no bias, then changing the values ​​of the variables through the pointer D, we would change the variables of the object of class C, which do not belong to the variables inherited from the class D (if the pointer pD had the same meaning as pC, then when pD-> f was inverted, we would actually work with variable
a).

Bottom line: static_cast, when working with a class hierarchy, determines the values ​​of pointers so that accessing class variables through a pointer is correct.

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

Consider the following code:

int main(){
    C* pC = new C;
    A* pA = static_cast(pC);
    D* pD = static_cast (pC);
    B* pB = static_cast (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 have a value other than 0? Consider the code line by line:

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

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. During the conversion, static_cast checks that such a hierarchy really takes place (i.e., that class B is an inheritor of class A), but it does not verify that the object pointed to by the pointer 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 (because pB is completely sure that it points to the object type B), we actually write the data e in the variable f, inherited from class D. And pD pointer will interpret the information recorded in the variable function f, as float, which is what we see when outputting through cout.

How to solve such a problem?
To do this, use dynamic_cast, which checks not only the validity of the class hierarchy, but also the fact that the pointer does point to an object of the type we want to cast to.

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

Demonstration of the solution to the problem, with the same class hierarchy:

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

I suggest running the code and make sure that the operation

B* pB = dynamic_cast (pA)

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

I don’t give any links, the source is personal experience.

Thanks to all!

Also popular now: