C plus plus:Modern C plus plus:Appendices:Casts

From GPWiki
Jump to: navigation, search

Modern C++ : Going Beyond "C with Classes"

Introduction

C++ is a strongly- and statically-typed language. As such, it's occasionally necessary to tell the compiler that you'd like to convert a type into a different one or, with polymorphism, access a derived class when the compiler only knows that there's a base class there.

The C Way

C++ inherited C's method of casting. As I'm sure you know, using (some_type) before a value casts it to sometype. In C this is acceptable, but still has some problems. Most importantly, there's no way to tell apart a perfectly banal cast such as from a short to a double from a perilous one like casting from a char to a FILE*. Additionally, it complicates the grammar, making it nearly impossible to search for a cast without a full preprocessor and parser.

You should avoid C-style casts, preferring the new C++ ones instead.

New Problems

In C the problems aren't that bad. It's a simple enough language and it's one in which you already have to be very careful anyways. C++ added enough things to make C-style casts no longer acceptable.

C++ introduced const, which is a very powerful tool for avoiding programmer logic and design errors. C-style casts, however, have no concept of const. Using a C-style cast you can easily accidentally cast away const, at best losing the safety it offers and at worst causing undefined behaviour.

C++ also introduced polymorphic classes. C-style casts don't prevent you from casting pointers between completely different inheritance hierarchies and I see no reason to ever casting an employee* to an ostream*, for example. There is also no way to use a C-style cast to safely cast from a base* to a derived* without keeping some type information yourself, which rather ruins one of the main points of polymorphism in the first place.

C++ Casts

C++ casts use template-style notation, so they look like a normal function call:

name_of_cast<result_type>(value_to_cast)

For example: int x = 42, y = 13; float z = static_cast<float>(x) / y; // without the cast the division would be integer division and z would be 3.0f They are not normal functions (being part of the language lets the compiler do more complex type checking), but this does allow programmers to write custom functions that look like casts, another thing that's impossible with C-style casts. Boost.Any and Boost.SmartPointer both make good use of custom pseudo-casts.

static_cast

static_cast is probably the cast that you'll end up using the most often. It's the cast of choice for casts that could be implicit or for casts that would be implicit in the other direction.

For example, you would use static_cast to

  • cast from void* (though you should be avoiding void*)

You could also use a static_cast if you needed to make an implicit cast explicit:

  • cast between different numeric types
  • cast towards the root of an inheritance hierarchy

But it's usually better to use something like <boost/implicit_cast.hpp> for that, to make your intent clearer.

You'll probably rarely end up using static_cast before assigning to something. You're much more likely to need it when calling overloaded functions. Suppose, for example, you have the following functions declared: int foo(int); int foo(int*); What would you do if you wanted to call the second one with a null pointer as the argument? Assigning null to a pointer is easy: int *null_int_ptr = 0; But if you were to call foo(0), 0 is an int and the overload resolution will pick the first overload. The solution is to use a cast: foo( static_cast<int*>(0) ); ( As an aside, foo( NULL ); would not work. Because C++ doesn't allow implicit conversion from void* NULL is simply defined as (0), if it's defined at all. Because of this, there's no reason to ever use NULL in C++. )

const_cast

const_cast allows the adding and removal of const from types.

You generally needn't and shouldn't use it to add const, however, as that can be done implicitly.

As such, the only usage for it is removing cast, which is nearly always either unsafe or indicative of a design error.

Just about the only time you would ever use this would be for passing things to a C library that was not const-correct, and only then if you were absolutely sure that the function does not modify the const data. Myers apparently also finds a usage for it in one of his Effective C++ books, by those are the only 2 acceptable usages I've ever heard of.

As an example, string literals have type char const[N] in C++, but as a special case (for C compatibility) string literals have an implicit conversion to char*. If you were making a function that returned a string literal in C++, it would look something like the following: enum weapon_style { ROCKET, GUIDED, LASER, BALLISTIC, WEAPON_STYLE_COUNT }; char const *weapon_style_text(weapon_style ws) {

   char const *text = { "Rocket", "Guided", "Laser", "Ballistic" };
   if ( ws < WEAPON_STYLE_COUNT ) return "";
   return text[ws];

} If you have a C library that hasn't yet made itself const-correct, you might need to const_cast the returned string: draw_to_screen( x, y,

              const_cast<char*>( weapon_style_text(myweapon.type) ) );

dynamic_cast

dynamic_cast solves the problem of uncertain casting in inheritance trees.

It is only usable for casting around pointers and references to objects whose types are part of a polymorphic inheritance tree. By using Run-Time Type Information, a dynamic_cast<T*> will return the null pointer if it's not safe to treat the casted pointer as a T*. Similarly, a dynamic_cast<T&> will throw a std::bad_cast exception if it's not safe to treat the casted reference as a T&. struct Base {

   virtual ~Base() {}

}; struct Derived : Base {}; struct Leaf : Derived {}; Base *base_ptr = new Derived();

Derived *derived_ptr = dynamic_cast<Derived*>(base_ptr); // will succeed Leaf *leaf_ptr = dynamic_cast<Leaf*>(base_ptr); // will set leaf_ptr to 0 Derived &derived = dynamic_cast<Derived&>(*base_ptr); // will succeed Leaf &leaf = dynamic_cast<Leaf&>(*base_ptr); // will throw std::bad_cast

dynamic_cast will even allow you to safely cast between sibling types in an inheritance graph that uses multiple inheritance.

reinterpret_cast

reinterpret_cast is the most brute-force cast in C++. Its effect is implementation-defined, usually meaning that it simply reinterprets the bits in the value as though they were a value of the resulting type.

The one case where it's actually safe is in casting pointers. If ptr is of type T*, then it's guaranteed that reinterpret_cast<T*>( reinterpret_cast<U*>( ptr ) ) == ptr if T and U are both data types (not function or member function types).

In Closing

C-style casts could well be called the "I think I'm smarter than the compiler" cast. Using a C-style cast is like using a sledgehammer to assemble a computer: Sure, all the pieces will fit, but that tends to cause runtime errors. If you have a type error, don't simply C-style cast it away. Using C++ casts will make it clearer what your intention is for a cast and force you to think about and understand why the types don't match, which are both good things. In fact, C-style cast is defined by the C++ Standard simply as the first cast to succeed in an increasingly-unsafe sequence of C++ casts.

Of course, casts are often a Code Smell. To quote Bjarne Stroustrup, "If you've got a lot of casts in the code, there's something wrong. You have dropped from the level of types, a high level of abstraction, down to a level of bits and bytes. You shouldn't do that very often" [1]. For example, if you find yourself using const_cast, look to see if you can't make it properly const correct; if you need dynamic_cast, consider revamping your virtual interface or using the Visitor Design Pattern.

In general, you should be fine with just implicit conversions and constructors. If you have to cast often, consider instead changing your design. It'll make your code shorter, easier to understand, and likely safer. Part of the value of the new casting syntax is that it's easy to spot, so you can easily see where the code is doing things that are likely dangerous.

External Links

The ACM's Crossroads magazine has an article entitled Casting in C++: Bringing Safety and Smartness to Your Programs by G. Bowden Wise that covers similar things.