tags:

views:

131

answers:

2
+7  Q: 

static_cast safety

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

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

+10  A: 

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*.

Steve Jessop
Multiple inheritance isn't the only problem, AndreyT's answer addresses the issue of downcasting to the wrong type.
Patrick Johnmeyer
True, I only fully answered the first paragraph of the question, and partially the second para. Not the broad issue (in the title) of static_cast safety.
Steve Jessop
+4  A: 

No, your "AFAIK" is incorrect. static_cast never behaves as reinterpret_cast (except, maybe when you convert to void *, although this conversion is not normally supposed to be carried out by reinterpret_cast).

Firstly, when static_cast is used for pointer or reference conversions, the specification of the static_cast explicitly requires a certain relationship to exist between the types (and be known to static_cast). For class types, they shall be related by inheritance, as perceived by static_cast. It is not possible to satisfy that requirement without having both types completely defined by the point of static_cast. So, if the definition(s) is(are) not visible at the point of static_cast, the code simply will not compile.

To illustrate the above with examples: static_cast can be used [redundantly] to perform object pointer upcasts. The code

Derived *derived = /* whatever */;
Base *base = static_cast<Base *>(derived);

is only compilable when the following code is compilable

Base *base(derived);

and for this to compile the definition of both types have to be visible.

Also, static_cast can be used to perform object pointer downcasts. The code

Base *base = /* whatever */;
Derived *derived = static_cast<Derived *>(base);

is only compilable when the following code is compilable

Base *base(derived); // reverse direction

and, again, for this to compile the definition of both types have to be visible.

So, you simply won't be able to use static_cast with undefined types. If your compiler allows that, it is a bug in your compiler.

static_cast can be unsafe for pointers/references for a completely different reason. static_cast can perform hierarchical downcasts for object pointer/reference types without checking the actual dynamic type of the object. static_cast can also perform hierarchical upcasts for method pointer types. Using the results of these unchecked casts can lead to undefined behavior, if done without caution.

Secondly, when static_cast is used with arithmetic types, the semantics is totally different and has nothing to do with the above. It just performs arithmetic type conversions. They are always perfectly safe (aside form the range issues), as long as they fit your intent. In fact, it might be a good programming style to avoid static_cast for arithmetic conversions and use old C-style casts instead, just to provide a clear differentiation in the source code between always-safe arithmetic casts and potentially-unsafe hierarchical pointer/reference casts.

AndreyT
"use old C_style casts instead" - that's what I usually do too, but then someone complains and we end up arguing whether there's any difference between a C-style cast that they say is evil, and a single-argument constructor that they're perfectly happy with ;-)
Steve Jessop
so, if static_cast doesn't compiles for undefined pointer/references, it can be cconsidered as safe for that type of conversions?
dimba
@idimba: Er... I have hard time trying to apply notions of "safe" and "unsafe" to code that doesn't compile. I'd even say that it makes little sense.
AndreyT