Hy,
I'm coming from my happy Java environment and I am trying to
understand some aspects of the battle field that is C++.
I'm currently reading "Thinking in C++" and have reached the part
where Bruce Eckel introduces the possible casts C++ provides.
I can easily understand the need for the normal cast (static_cast),
but why do you need const_cast or reinterpret_cast, when they cause so
much trouble?
I mean, if I or somebody else declare something has to be constant or
of a certain type, I/he/she/it don't/doesn't want that these
properties are just changed, because we* have thought this through
beforehand.
Hope I get a lot of nice answers.
A lot of this has been mentioned else-thread, but here's my specific
take on it:
C is meant, in one form or another, as a portable assembly language,
with some efficient "syntactic sugar". Specifically, it is envisioned
that there is an obvious, one to one translation from C code to
assembly for most platforms which will perform about as well as the
handwritten assembly. This is largely the case (though with platform
specific knowledge and hackery, or hardware specific assembly, yes you
can do better than C). C comes with syntactic sugar, such as functions
and structs, which is meant as literal syntactic sugar: easily
translatable to other obvious language constructs without additional
execution time nor execution space costs.
C++ is meant in many ways to be a better C, or at least that's one of
its explicit design goals. As such, you need to be able to do weird
things like take a memory address, do int arithmetic on it, do some
double arithmetic on it, then treat it as a pointer and fetch the
pointed-to memory location. Generally such things are not required by
nearly all C and C++ programs, but it's the C and C++ philosophy at
some level to be a highly efficient portable assembly language. Also,
as C++ is meant to be a better C, C++ tries as best as it can to be
backwards compatible with C.
So, C let's you do this weird typecasting, thus C++ should also let
you do this weird typecasting. The designers of C++ saw that such a
certain subsets of typecasting is quite usually a bad idea. However,
there are good reasons to do such weird typecasting in C and C++,
usually to work with an existing C library interface (ex: POSIX) which
requires such typecasting (ex: you're required to reinterpret_casting
or equivalent the result of dlsym), to be platform specific stuff like
write device drivers, or to do generics in C and sometimes C++ (but C+
+ has templates which are usually a better solution).
So, these certain subsets of typecasting are quite usually bad and not
intended, but the syntax makes it easy for the developer to do, so a
new syntax was invented which easily allows developers to do all of
their old things but in a not "ambiguous" way - the new named casts
static_cast, const_cast, and reinterpret_cast. The new names are also
easier to grep for, or visually see. It's for the developer's benefit,
to allow him to help prevent shooting himself in the foot through a
typo or equivalent.
Also, with the introduction of multiple inheritance and virtual
inheritance, under the "normal" implementation, typecasting in C++ can
change the actual memory bit patterns. Before, a C cast from a pointer
to an integer (of sufficient size) generally did not change the
underlying bit patterns. However, to implement multiple inheritance
and virtual inheritance, pointer casting in C++ can change the actual
bit pattern of the pointer. Static offset calculations are needed to
adjust the pointer when working with multiple inheritance under the
"normal" implementation. Ex:
class A;
class B;
B* get_some_b_object();
B* b = get_some_b_object();
A* a = (A*)b;
Suppose the developer knows that A is a subtype of B, and he knows
that the object returned is actually an "A" object, so this cast
"makes sense". However, if the types A and B are only forward
declared, then the cast will be treated as a reinterpret_cast, which
will highly likely not change the bit patterns involved, likely
breaking the example if this has multiple and/or virtual inheritance.
(It's also (??) formally undefined behavior.) The problem is that C++
effectively defines the C-style cast as having two different behaviors
depending on whether the types are related through an inheritance
relationship at the point of the cast. If someone later adds or
removes the definitions of A and B from scope (such as adding or
removing a header in a header in a header), then the C-style cast may
silently change behavior and break the program. The static_cast has
only one meaning, and it will only compile when the definitions of A
and B are in scope, which is likely what the programmer wants.
So, to summarize, why does C++ have the "evil" casts?
- Backwards compatible with C as an explicit design goal.
- C++ designers also recognized that these "evil" casts sometimes have
legitimate uses.
- C++ designers also recognized that a more clear syntax for casting
would help developers not shoot themselves in the foot, and for
auditing, code review, etc.
- C++ designers also recognized that the C-style cast has a new
ambiguity in C++ which it did not have in C, so new named casts help
developers not shoot themselves in the foot, and for auditing, code
review, etc.