Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

static_cast safety

AFAIK, for pointers/references static_cast, if a class definition is not visible to compiler at this point, then static_cast will be behave like reinterpret_cast.

Why is static_cast unsafe for pointers/references and is safe for numeric values?

like image 805
dimba Avatar asked Mar 04 '10 19:03

dimba


People also ask

What happens when static_cast fails?

As we learnt in the generic types example, static_cast<> will fail if you try to cast an object to another unrelated class, while reinterpret_cast<> will always succeed by "cheating" the compiler to believe that the object is really that unrelated class.

Should I use static_cast?

This is used for the normal/ordinary type conversion. This is also the cast responsible for implicit type coersion and can also be called explicitly. You should use it in cases like converting float to int, char to int, etc.

What static_cast is actually doing?

static_cast can be used to convert between pointers to related classes (up or down the inheritance hierarchy). It can also perform implicit conversions.

Does static_cast throw?

static_cast can't throw exception since static_cast is not runtime cast, if some cannot be casted, code will not compiles. But if it compiles and cast is bad - result is undefined.


1 Answers

In short, because of multiple inheritance.

In long:

#include <iostream>

struct A { int a; };
struct B { int b; };
struct C : A, B { int c; };

int main() {
    C c;
    std::cout << "C is at : " << (void*)(&c) << "\n";
    std::cout << "B is at : " << (void*)static_cast<B*>(&c) << "\n";
    std::cout << "A is at : " << (void*)static_cast<A*>(&c) << "\n";

}

Output:

C is at : 0x22ccd0
B is at : 0x22ccd4
A is at : 0x22ccd0

Note that in order to convert correctly to B*, static_cast has to change the pointer value. If the compiler didn't have the class definition for C, then it wouldn't know that B was a base class, and it certainly wouldn't know what offset to apply.

But in that situation where no definition is visible, static_cast doesn't behave like reinterpret_cast, it's forbidden:

struct D;
struct E;

int main() {
    E *p1 = 0;
    D *p2 = static_cast<D*>(p1); // doesn't compile
    D *p3 = reinterpret_cast<D*>(p1); // compiles, but isn't very useful
}

A plain C-style cast, (B*)(&c) does what you say: if the definition of struct C is visible, showing that B is a base class, then it's the same as a static_cast. If the types are only forward-declared, then it's the same as a reinterpret_cast. This is because it's designed to be compatible with C, meaning that it has to do what C does in cases which are possible in C.

static_cast always knows what to do for built-in types, that's really what built-in means. It can convert int to float, and so on. So that's why it's always safe for numeric types, but it can't convert pointers unless (a) it knows what they point to, and (b) there is the right kind of relationship between the pointed-to types. Hence it can convert int to float, but not int* to float*.

As AndreyT says, there is a way that you can use static_cast unsafely, and the compiler probably won't save you, because the code is legal:

A a;
C *cp = static_cast<C*>(&a); // compiles, undefined behaviour

One of the things static_cast can do is "downcast" a pointer to a derived class (in this case, C is a derived class of A). But if the referand is not actually of the derived class, you're doomed. A dynamic_cast would perform a check at runtime, but for my example class C you can't use a dynamic_cast, because A has no virtual functions.

You can similarly do unsafe things with static_cast to and from void*.

like image 193
Steve Jessop Avatar answered Oct 06 '22 03:10

Steve Jessop