Explicit
cast syntax
Whenever
you use a cast, you’re breaking the type
system. [62]
You’re telling the compiler that even though you know an object is a
certain type, you’re going to pretend it is a different type. This is an
inherently dangerous activity, and a clear source of errors.
Unfortunately,
each cast is different: the name of the pretender type surrounded by
parentheses. So if you are given a piece of code that isn’t working
correctly and you know you want to examine all casts to see if they’re
the source of the errors, how can you guarantee that you find all the casts? In
a C program, you can’t. For one thing, the C compiler doesn’t
always require a cast (it’s possible to assign dissimilar types
through
a void pointer without being forced to use a cast), and the casts all look
different, so you can’t know if you’ve searched for every one.
To
solve this problem, C++ provides a consistent casting syntax using four
reserved words:
dynamic_cast
(the subject of the first part of this chapter),
const_cast,
static_cast,
and
reinterpret_cast.
This window of opportunity opened up when the need for
dynamic_cast
arose – the meaning of the existing cast syntax was already far too
overloaded to support any additional functionality.
By
using these casts instead of the
(newtype)
syntax, you can easily search for
all the casts in any program. To support existing code, most compilers have
various levels of error/warning generation that can be turned on and off. But
if you turn on full errors for the explicit cast syntax, you can be guaranteed
that you’ll find all the places in your project where casts occur, which
will make bug-hunting much easier.
The
following table describes the different forms of casting:
|
For
“well-behaved” and “reasonably well-behaved” casts,
including things you might now do without a cast (e.g., an upcast or automatic
type conversion).
|
|
To
cast away
const
and/or
volatile.
|
|
For
type-safe downcasting (described earlier in the chapter).
|
|
To
cast to a completely different meaning. The key is that you’ll need to
cast back to the original type to use it safely. The type you cast to is
typically used only for bit twiddling or some other mysterious purpose. This is
the most dangerous of all the casts.
|
The
three explicit casts will be described more completely in the following sections.
static_cast
A
static_cast
is used for all conversions that are well-defined. These include
“safe” conversions that the compiler would allow you to do without
a cast and less-safe conversions that are nonetheless well-defined. The types
of conversions covered by
static_cast
include typical castless conversions, narrowing (information-losing)
conversions, forcing a conversion from a
void*,
implicit type conversions, and static navigation of class hierarchies:
//: C24:Statcast.cpp
// Examples of static_cast
class Base { /* ... */ };
class Derived : public Base {
public:
// ...
// Automatic type conversion:
operator int() { return 1; }
};
void func(int) {}
class Other {};
int main() {
int i = 0x7fff; // Max pos value = 32767
long l;
float f;
// (1) typical castless conversions:
l = i;
f = i;
// Also works:
l = static_cast<long>(i);
f = static_cast<float>(i);
// (2) narrowing conversions:
i = l; // May lose digits
i = f; // May lose info
// Says "I know," eliminates warnings:
i = static_cast<int>(l);
i = static_cast<int>(f);
char c = static_cast<char>(i);
// (3) forcing a conversion from void* :
void* vp = &i;
// Old way produces a dangerous conversion:
float* fp = (float*)vp;
// The new way is equally dangerous:
fp = static_cast<float*>(vp);
// (4) implicit type conversions, normally
// Performed by the compiler:
Derived d;
Base* bp = &d; // Upcast: normal and OK
bp = static_cast<Base*>(&d); // More explicit
int x = d; // Automatic type conversion
x = static_cast<int>(d); // More explicit
func(d); // Automatic type conversion
func(static_cast<int>(d)); // More explicit
// (5) Static Navigation of class hierarchies:
Derived* dp = static_cast<Derived*>(bp);
// ONLY an efficiency hack. dynamic_cast is
// Always safer. However:
// Other* op = static_cast<Other*>(bp);
// Conveniently gives an error message, while
Other* op2 = (Other*)bp;
// Does not.
} ///:~
In
Section (1), you see the kinds of conversions you’re used to doing in C,
with or without a cast. Promoting from an
int
to a
long
or
float
is not a problem because the latter can always hold every value that an
int
can contain. Although it’s unnecessary, you can use
static_cast
to highlight these promotions.
Converting
back the other way is shown in (2). Here, you can lose data because an
int
is not as “wide” as a
long
or a
float
– it won’t hold numbers of the same size. Thus these are called
“narrowing conversions.”
The compiler will still perform these, but will often give you a warning. You
can eliminate this warning and indicate that you really did mean it using a cast.
Assigning
from a
void*
is not allowed without a cast in C++ (unlike C), as seen in (3). This is
dangerous and requires that a programmer know what he’s doing. The
static_cast,
at least, is easier to locate than the old standard cast when you’re
hunting for bugs.
Section
(4) shows the kinds of implicit type conversions that are normally performed
automatically by the compiler. These are automatic and require no casting, but
again
static_cast
highlights the action in case you want to make it clear what’s happening
or hunt for it later.
If
a class hierarchy has no
virtual
functions or if you have other information that allows you to safely downcast,
it’s slightly faster to do the downcast
statically than with
dynamic_cast,
as shown in (5). In addition,
static_cast
won’t allow you to cast out of the hierarchy, as the traditional cast
will, so it’s safer. However, statically navigating class hierarchies is
always risky and you should use
dynamic_cast
unless you have a special situation.
const_cast
If
you want to convert from a
const
to a non
const
or from a
volatile
to a non
volatile,
you use
const_cast.
This is the
only
conversion allowed with
const_cast;
if any other conversion is involved it must be done separately or you’ll
get a compile-time error.
//: C24:Constcst.cpp
// Const casts
int main() {
const int i = 0;
int* j = (int*)&i; // Deprecated form
j = const_cast<int*>(&i); // Preferred
// Can't do simultaneous additional casting:
//! long* l = const_cast<long*>(&i); // Error
volatile int k = 0;
int* u = const_cast<int*>(&k);
}
class X {
int i;
// mutable int i; // A better approach
public:
void f() const {
// Casting away const-ness:
(const_cast<X*>(this))->i = 1;
}
}; ///:~
If
you take the address of a
const
object, you produce a pointer to a
const,
and this cannot be assigned to a non
const
pointer without a cast. The old-style cast will accomplish this, but the
const_cast
is the appropriate one to use. The same holds true for
volatile. If
you want to change a class member inside a
const
member function, the traditional approach is to cast away constness
by saying
(X*)this.
You can still cast away constness using the better
const_cast,
but a superior approach is to make that particular data member
mutable,
so it’s clear in the class definition, and not hidden away in the member
function definitions, that the member may change in a
const
member function.
reinterpret_cast
This
is the least safe of the casting mechanisms, and the one most likely to point
to bugs. At the very least, your compiler should contain switches to allow you
to force the use of
const_cast
and
reinterpret_cast,
which will locate the most unsafe of the casts.
A
reinterpret_cast
pretends that an object is just a bit pattern that can be treated (for some
dark purpose) as if it were an entirely different type of object. This is the
low-level bit twiddling that C is notorious for. You’ll virtually always
need to
reinterpret_cast
back to the original type before doing anything else with it.
//: C24:Reinterp.cpp
// Reinterpret_cast
// Example depends on VPTR location,
// Which may differ between compilers.
#include <cstring>
#include <fstream>
using namespace std;
ofstream out("reinterp.out");
class X {
enum { sz = 5 };
int a[sz];
public:
X() { memset(a, 0, sz * sizeof(int)); }
virtual void f() {}
// Size of all the data members:
int membsize() { return sizeof(a); }
friend ostream&
operator<<(ostream& os, const X& x) {
for(int i = 0; i < sz; i++)
os << x.a[i] << ' ';
return os;
}
virtual ~X() {}
};
int main() {
X x;
out << x << endl; // Initialized to zeroes
int* xp = reinterpret_cast<int*>(&x);
xp[1] = 47;
out << x << endl; // Oops!
X x2;
const int vptr_size= sizeof(X) - x2.membsize();
long l = reinterpret_cast<long>(&x2);
// *IF* the VPTR is first in the object:
l += vptr_size; // Move past VPTR
xp = reinterpret_cast<int*>(l);
xp[1] = 47;
out << x2 << endl;
} ///:~
The
class
X
contains some data and a
virtual
member function. In
main( ),
an
X
object is printed out to show that it gets initialized to zero, and then its
address is cast to an
int*
using a
reinterpret_cast.
Pretending it’s an
int*,
the object is indexed into as if it were an array and (in theory) element one
is set to 47. But here’s the output:
[63]
0 0 0 0 0
47 0 0 0 0
Clearly,
it’s not safe to assume that the data in the object begins at the
starting address of the object. In fact, this compiler puts the VPTR at the
beginning of the object, so if
xp[0]
had been selected instead of
xp[1],
it would have trashed the VPTR.
To
fix the problem, the size of the VPTR is calculated by subtracting the size of
the data members from the size of the object. Then the address of the object is
cast (again, with
reinterpret_cast)
to
a
long,
and the starting address of the actual data is established,
assuming
the VPTR is placed at the beginning of the object. The resulting number is cast
back to an
int*
and the indexing now produces the desired result:
Of
course, this is inadvisable and nonportable programming. That’s the kind
of thing that a
reinterpret_cast
indicates, but it’s available when you decide you have to use it.
[62]
See Josée Lajoie , “The new cast notation and the bool data
type,” C++ Report, September, 1994 pp. 46-51.
[63]
For this particular compiler. Yours will probably be different.
Go to CodeGuru.com
Contact: webmaster@codeguru.com
© Copyright 1997-1999 CodeGuru