A blog about in-depth analysis and understanding of programming, with a focus on C++.

Thursday, September 20, 2007

Cast in Gold

Casting. Casts are that dirty little thing that everyone knows not to use but everyone uses it more often than ought to.

C introduced us to one kind of cast. You have a thing of type A, and you can try to turn it into a thing of type B. Simple construct for a simple language.

C++ created more casts. But there's a funny thing about C++ casts; nobody likes them. Articles have been written about why they should be avoided and why they were a bad idea for the language. Few C++ programmers prefer them over C-style casts.

This article will explain what these C++-style casts are and why you should use them a lot more often that you probably do.

All of these casts share a common syntax. It reads like, cast_name<TypeToConvertTo>(expressionToCast); No matter how small "cast_name" is, it will always be bigger than the nothing that C-style casts use. C++-style casts draw attention to themselves by being large and ugly.

This was supposedly done intensionally as a way to discourage their use. Unfortunately, it worked; that's why most everybody just uses C-style casts. The idea was that it would discourage the use of casting in general, but that was madness.

There are 4 kinds of C++ casts. There is the static_cast, const_cast, reinterpret_cast, and dynamic_cast. Each of them is meant to be used under specific circumstances.

The easiest two to defend are reinterpret and dynamic. This is because neither of them have a direct analog with C-style casts.

If you have a 32-bit float value, and you want to get it's bit-pattern as an unsigned 32-bit integer, in C, you have to do this:
unsigned int32_t iFloatBits = *(unsigned int32_t *)&fValue;

Not only is this somewhat obtuse as to what's happening (you're pretending a float-pointer is an integer pointer), it requires that fValue have a memory address, since you have to dereference it. This is highly unnecessary; what you're really asking is for the compiler to bring fValue's bits from a float register into an int register. This is very simple and just about every CPU can do it. But in C, you can't actually say it like that.

In C++, you have:
unsigned int32_t iFloatBits = reinterpret_cast<unsigned int32_t>(fValue);

The only downside is the sheer length of the expression. But it's a lot more obvious what's going on, so long as the reader understands what C++ casts do.

As for dynamic_cast, there is no C analog because C doesn't have inheritance. The purpose of this operation is to take a pointer to a base class and turn it into a derived class. Now, of course, that particular base class instance may not actually be that particular derived class. So a regular C-style cast operation is right out, since they are compile-time operations.

See, dynamic_cast isn't a cast at all really; it's a function call. One that can fail. That is, a particular cast can return NULL if the cast operation cannot work on the specific instance. A dynamic cast is what allows some degree of runtime-type identification. You use it when you need to break the OOP contract and consider a base class to be a derived class.

So, it is obvious that both of these are superior to their C analogs (and one doesn't even have a C analog, so it is a priori superior to nothing). However, what of the other two?

Well, the main advantage of using const_cast is just error checking. A const cast can only add or remove const-ness from a type. As such, you cannot accidentally convert the type to something else by entering the wrong type. This is useful.

Also, removing const-ness (which is 99.9% of all const_cast uses, since adding const-ness is implicit) is rather rare. It usually happens when interfacing with code that you did not write. Indeed, doing this kind of cast suggests a design flaw or that something ought to be made mutable. Outside of that, the times when this kind of cast is truly needed are few indeed. So the larger size of the conversion expression is not a substantial problem.

Which brings us to static_cast. This is the cast that I can recommend the least. It gives protection from accidentally doing something that reinterpret or const casts should be used for, but that's about it. And so long as we assume that the programmer is intelligent, and therefore is not going to arbitrarily be doing these kinds of conversions, then we should assume that each static cast operation is actually necessary. And if the programmer actually is not intelligent, the ugliness of static_cast will not be a deterrent, since they can always use C-style casts anyway.

So, either way, it generally better to use C-style casts when you might use a static_cast.

No comments: