Critique each
of the following C++ casts for style and correctness.
First, a general note: All of the following
dynamic_casts would be errors if the classes involved did
not have virtual functions. Fortunately, A does provide a
virtual function, making all the dynamic_casts legal.
void g()
{
unsigned char* puc = static_cast<unsigned char*>(&c);
signed char* psc = static_cast<signed char*>(&c);
Error: We must use reinterpret_cast for
both cases. This might surprise you at first, but the reason is
that char, signed char, and unsigned
char are three distinct types. Even though there are implicit
conversions between them, they are unrelated, so pointers to them
are unrelated.
void* pv = static_cast<void*> (&b1);
B* pb1 = static_cast<B*>(pv);
These are both fine, but the first is
unnecessary, because there is already an implicit conversion from a
data pointer to a void*.
B* pb2 = static_cast<B*> (&b1);
This is fine, but unnecessary, since the
argument is already a B*.
A* pa1 = const_cast<A*>(&ra1);
This is legal, but casting away const
(or volatile) is usually indicative of poor style. Most of
the cases in which you legitimately would want to remove the
const-ness of a pointer or reference are related to class
members and covered by the mutable keyword. See Item 43 for further
discussion of const-correctness.
Guideline
|
Avoid casting away
const. Use mutable instead.
|
A* pa2 = const_cast<A*>(&ra2);
Error: This will produce undefined behavior if
the pointer is used to write on the object, because a2
really is a const object. To see why this is a legitimate
problem, consider that a compiler is allowed to see that
a2 is created as a const object and use that
information to store it in read-only memory as an optimization.
Casting away const on such an object is obviously
dangerous.
Guideline
|
Avoid casting away
const.
|
B* pb3 = dynamic_cast<B*>(&c1);
Potential error (if you try to use
pb3): Because c1 IS-NOT-A B (because
C is not publicly derived from B—in fact, it is
not derived from B at all), this will set pb3 to
null. The only legal cast would be a reinterpret_cast, and
using that is almost always evil.
A* pa3 = dynamic_cast<A*>(&b1);
Probable error: Because b1 IS-NOT-AN
A (because B is not publicly derived from
A; its derivation is private), this is illegal unless
g() is a friend of B.
B* pb4 = static_cast<B*>(&d1);
This is fine, but unnecessary because
derived-to-public-base pointer conversions can be done
implicitly.
D* pd = static_cast<D*>(pb4);
This is fine, which may surprise you if you
expected this to require a dynamic_cast. The reason is
that downcasts can be static when the target is known, but beware:
You are telling the compiler that you know for a fact that what is
being pointed to really is of that type. If you are wrong, then the
cast cannot inform you of the problem (as could
dynamic_cast, which would return a null pointer if the
cast failed) and, at best, you will get spurious run-time errors
and/or program crashes.
Guideline
|
Avoid
downcasts.
|
pa1 = dynamic_cast<A*>(pb2);
pa1 = dynamic_cast<A*>(pb4);
These two look very similar. Both attempt to use
dynamic_cast to convert a B* into an A*.
However, the first is an error, while the second is not.
Here's the reason: As noted above, you cannot
use dynamic_cast to cast a pointer to what really is a
B object (and here pb2 points to the object
b1) into an A object, because B inherits
privately, not publicly, from A. However, the second cast
succeeds because pb4 points to the object d1, and
D does have A as an indirect public base class
(through C), and dynamic_cast is able to cast
across the inheritance hierarchy using the path B*
D*
C*
A*.
C* pc1 = dynamic_cast<C*>(pb4);
This, too, is fine for the same reason as the
last: dynamic_cast can navigate the inheritance hierarchy
and perform cross-casts, so this is legal and will succeed.
C& rc1 = dynamic_cast<C&>(*pb2);
}
Finally, an "exceptional" error: Because
*pb2 isn't really a C, dynamic_cast will
throw a bad_cast exception to signal failure. Why? Well,
dynamic_cast can and does return null if a pointer cast
fails, but since there's no such thing as a null reference, it
can't return a null reference if a reference cast fails. There's no
way to signal such a failure to the client code besides throwing an
exception, so that's what the standard bad_cast exception
class is for.