There are a few small items in C++ that should be marked not for casual use. Casting is one of them; Steve makes it clear as to why.
As is usual with a deadline approaching, I was casting about for a topic for this months column and decided that casting might be a good choice. We all know that overuse of casting is an egregious habit that bespeaks poor manners, but what is the basis of this social idiom? After all, manners are culturally defined and usually have (at least historically) some practical motivation. As anyone who must regularly satisfy the curiosity of young children or marketers knows, its not always easy to unearth the original motivation for certain social mores. Why dont we talk with our mouths full? Easy. Its not practical to choke and expire at the dinner table. Why do Americans eat with their left hands in their laps, while Europeans put their left hands on the table? Pause. Americans need to have quick access to their firearms during meals. Why cant I make a castle out of my vegetables? Count to ten. Because I say so!
In an earlier column, we discussed the socially gauche and technically disastrous approach of asking personal questions of an object through the use of dynamic casts [1]. There, we trashed both type queries and capability queries, with a few glancing blows thrown at managers and Java. This month well look at non-dynamic casting. In effect, well see that improper use of static casts is the inverse of a type query. Instead of asking probing personal questions of an object, a static cast denies an object the recognition of its identity and tries to force it to act outside the range of its innate capabilities. Think about what would happen if one of your colleagues from marketing were suddenly told to write C++ code. Thats a static cast.
Old and New Casts
Dont use old-style casts. They simply do too much for and to you, and are entirely too easy to use. Consider a header file:
// emp.h //... const Person *getNextEmployee(); //...This header is used throughout the application, including in the following section of code.
#include "emp.h" //... Person *victim = (Person *)getNextEmployee(); dealWith( victim ); //...Now, any casting away of constness is potentially dangerous and non-portable. Suppose, however, the author of this code is more clairvoyant than the rest of us and has determined that this particular use of the cast is correct and portable. The code is still wrong for two reasons. First, the conversion requested is much stronger than is necessary. Second, the author has fallen into the beginners fallacy of depending on secondary semantics; this code assumes that the unadvertised meanings of the abstraction expressed by the getNextEmployee function will continue to be supported in the future.
Essentially, this use of getNextEmployee assumes that the function will never change after its initial implementation. Of course, this is not the case. Soon the implementer of the emp.h header file will recognize that employees are not people and correct the design accordingly:
// emp.h //... const Employee *getNextEmployee(); //...Unfortunately, the cast is still legal, although it has changed its meaning from modifying the constness of its object to changing the set of operations available on the object. In using the cast, we are telling the compiler that we know more about the type system than the compiler does. Originally, this may have been the case, but when the header file is maintained, it is unlikely that all uses of the header will be revisited, and our imperious command to the compiler will stand uncorrected. The use of the appropriate new-style cast would have allowed the compiler to detect the change in usage and flag the error:
#include "emp.h" //... Person *victim = const_cast<Person *> (getNextEmployee()); dealWith( victim );Note that the use of a const_cast, while an improvement over an old style cast, is still dangerous. We are still relying on the assumption that, under maintenance, the unadvertised and perhaps accidental collaboration between the functions getNextEmployee and dealWith that permit the const_cast will continue to hold. Very tenuous.
Casting under Multiple Inheritance
Under multiple inheritance, an object may have many valid addresses. Each base class subobject of a complete object may have a unique address, and each of these addresses is a valid address for the complete object.
class A { /*...*/ }; class B { /*...*/ }; class C : public A, public B { /*...*/ }; //... C *cp = new C; A *ap = cp; // OK B *bp = cp; // OKIn the example above, it is likely that the B subobject of the C complete object is allocated at a fixed offset, or delta, from the start of the C object. Conversion of the derived class pointer cp to B * will therefore result in adjustment of cp by the delta to produce a valid B address. The conversion is type safe, efficient, and automatically implemented by the compiler.
Similarly, the existence of multiple valid addresses for an object forces C++ to be precise about the meaning of pointer comparison:
if( bp == cp ) //...The question we are asking above is not Do these two pointers contain the same bit pattern?, but rather Do these two pointers refer to the same object? The implementation of the condition may be somewhat more complex, but is still efficient, safe, and automatic:
if( bp ? (char *)bp-delta==(char *)cp : cp==0 )Both old and new style casts may be used to perform conversions that respect delta arithmetic on class object addresses. However, unlike the conversions above, there is no guarantee that the result of the cast will be a valid address. (A dynamic_cast will give such a guarantee, but introduces other problems.)
B *gimmeAB(); bp = gimmeAB(); cp = static_cast<C *>(bp); cp = (C *) bp; typedef C *CP; cp = CP( bp );All three casts above will perform delta arithmetic on bp, but the result will be valid only if the B object to which bp refers is part of a containing C object. If this assumption is incorrect, the result will be a bad address, equivalent to some creative C-style code:
cp = (C *)((char *)bp-delta)A reinterpret_cast will do just what it says. It will simply reinterpret the bit pattern of its argument to mean something else, without modifying the bits. Effectively, it turns off the delta arithmetic.
// yes, I do want to dump core... cp = reinterpret_cast<C *>(bp);All these uses of casts are asking the object referred to by the B pointer to take on more responsibility than its interface can guarantee. As we saw in an earlier article, bad designs often result from using dynamic_cast to ask an object detailed questions about its type and capabilities. In this case, we have a bad design because we know too little about an objects capabilities, but are using a static cast to force it into a role that it may not be able to fulfill.
Casting Incomplete Types
Incomplete class types have no definition, but it is still possible to declare pointers and references to them and to declare functions that take arguments and return results of the incomplete types. This is a common and useful practice:
class Y; class Z; Y *convert( Z * );The problem arises when a programmer tries to force the issue; ignorance is bliss only to a certain extent:
Y *convert( Z *zp ) { return reinterpret_cast<Y *>(zp); }The reinterpret_cast is necessary here, because the compiler does not have any information available about the relationship between the types Y and Z. Therefore the best it can offer us is to reinterpret the bit pattern in the Z pointer as a Y pointer. This may even work for a while:
class Y { /*...*/ }; class Z : public Y { /*...*/ };It is likely that the Y base class subobject in a Z object has the same address as the complete object. However, this may not continue to be the case, and a remote change could affect the validity of the cast:
class X { /*...*/ }; class Z : public X, public Y { /*...*/ };The use of a reinterpret_cast causes the delta arithmetic to be turned off, and well get a bad Y address.
Actually, the reinterpret_cast is not the only choice above, since we could have used an old-style cast as well. This may initially seem the better choice, because an old-style cast will perform the delta arithmetic if it has enough information at its disposal. However, this flexibility actually compounds the problem, because we may get different behavior from the ostensibly same conversion depending on what information is available when the conversion is defined:
Y *convert( Z *zp ) { return (Y *)zp; } //... class Z : public X, public Y { //... }; //... Z *zp = new Z; Y *yp1 = convert( zp ); Y *yp2 = (Y *)zp; cout << zp << ' ' << yp1 << ' ' << yp2 << endl;The value of yp1 will match that of either zp or yp2 depending on whether the definition of convert occurs before or after the definition of class Z. The situation can become immeasurably more complex if convert is a template function with many instantiations in many different object files. In this case, the ultimate meaning of the cast may depend on the idiosyncrasies of your linker. The use of reinterpret_cast is preferable to that of an old-style cast, in this case, because it will be more consistently incorrect. My preference would be to avoid either.
void * Is a Cast
A void pointer can refer to any data and frequently does. Copying an address to a void pointer is logically equivalent to a reinterpret_cast of the address. While it is legal to copy an address into a void pointer and cast the result back to its original type, its not always easy to determine the original type. Consider a simple multiple inheritance hierarchy:
class Button { /*...*/ }; class Subject { /*...*/ }; class ObservedButton : public Subject, public Button { /*...*/ };Its unlikely that even a summer intern would purposefully lose and then attempt to resupply type information:
Button *badButton = (Button *)(void *) new ObservedButton;This sequence of conversions will (probably) result in a bad address. The conversion to void * disposes of all knowledge of the hierarchy, and the conversion from void * to Button cannot then perform the correct delta arithmetic. This is the sort of error that often arises when a sequence of conversions is separated in time and space. For example, a user interface toolkit may attempt to use void pointers for flexibility (otherwise known as abdication of design responsibility), which in turn requires that users of the toolkit resupply type information:
// toolkit header... typedef void *Widget; void setWidget( Widget ); Widget getWidget(); // elsewhere... ObservedButton *b = new ObservedButton; setWidget( b ); // somewhere else entirely... Button *bb = static_cast<Button *> (getWidget());Here we have the same error as previously, but its a collaborative effort among the toolkit designer, the creator of the Widget, and the user of the Widget.
A public interface that contains a void pointer is probably wrong. A public interface that requires a user to cast arguments or results is probably wrong.
Because I Say So!
The basic problem with static casts is that theyre static. In employing such a construct, we are asking the compiler to accept our version of an objects capabilities rather than the objects. While many uses of casts may result in code that is initially correct, that code is not able to adjust itself automatically to changes in an objects type structure. Because these changes are generally remote from the point of the cast, maintainers often do not modify the code containing the cast. At the same time, the cast has the additional effect of turning off any diagnostics the compiler would otherwise have provided.
Casts are not essentially evil, but they must be used in moderation, in private, and in such a way that future maintenance of code remote from the cast will not invalidate the cast. From a practical perspective, these requirements imply that one should, in general, avoid casting abstract data types, and most particularly abstract data types in a hierarchy.
Use of a static cast is often, as Scott Meyers has observed, a sign that negotiations between you and your compiler have broken down. In effect, a static cast not only tells your compiler because I say so (and, as with a similar discussion with a human interlocutor, guarantees the end of any useful communication), it shows a lack of respect to the public interface offered by the abstract data type that is being cast. Thoughtful, negotiated solutions that respect the advertised capabilities of objects often require more finesse than a heavy-handed cast, but generally result in more robust, portable, and useable code and interfaces.
Note
[1] S. C. Dewhurst. Dont Ask, Dont Tell, C++ Report, May, 2000.
Stephen C. Dewhurst is the president of Semantics Consulting, Inc., located among the cranberry bogs of southeastern Massachusetts. He specializes in C++ and object-oriented mentoring and consulting, and training in advanced C++ programming, STL, and design patterns. He may be reached at www.semantics.org.