6.7 Type Casting
Type casting is the act of viewing an object of a derived class as an object of its base class. For example, a Number object may be viewed as an object of the class Number or as an object of the class DisplayableNumber. Viewing an object of a derived class in this way is sensible because the derived class (e.g., Number) object has all of the properties (code and data) of the base class (e.g., DisplayableNumber) object. Because any operation that can be applied to a base class object can also be applied to a derived class object, it is safe to use an object of the derived class where an object of the base class is expected.
Type casting is the foundation for a powerful form of polymorphism. In the sense that it is used here, polymorphism refers to the ability to manipulate in a uniform manner objects that are of different classes. A base class defines the uniform set of methods that can be applied to any objects of the base class and, more importantly, any objects of any of its derived classes. The term type casting describes this technique because the exact "type" (i.e., class) of an object is "cast" away in favor of a more generic type (the base class of the object).
Through polymorphism it is possible to create flexible and extensible software structures. A software structure that is defined only in terms of the properties of a base class can, without change, accommodate any object of a derived class by type casting the derived class object to its base class type. In this sense, the software structure is very flexible as it can be applied to a wide class of specific objects. The NumberPanel defined below exemplifies this kind of flexibility. The NumberPanel is a manager for objects that are derived from DisplayableNumber; it treats the objects that it manages as DisplayableNumbers only, unaware of their exact derived class type. Thus, it is possible for a NumberPanel to manage any collection of Number, Cyclers, BiCyclers, etc. objects. The NumberPanel also illustrates why polymorphic structures are extensible: since they can be applied to any class derived from the base class, the derived class need not even be known at the time the polymorphic structure is designed and implemented. Thus, the NumberPanel can manage objects whose classes did not exist at the time the NumberPanel was built. Extensibility means that a software system can be extended by adding new derived classes whose objects can be manipulated by other, existing parts of the system.
Type casting does not change the object - it only changes what parts of the object are visible through that type cast. For example, a single Number object might be viewed as shown below. When viewed as a DisplayableNumber object, the single object reveals only the base class portion of its structure, but when viewed as a Number object, all of the object's structure is revealed.
There are two different forms for expressing the type cast in C++ and each form has two variations depending on whether the type cast results in a reference to an object or a pointer to an object. The difference is only one of syntax; all of these forms and variations achieve the same overall effect. Which form to use depends on stylisic consideration. Which variation (whether to use an object or a pointer to an object) is sometimes dictated by circumstances. The two different forms of type casting are
(BaseClassName) derivedClassObject and BaseClassName (derivedClassObject)
In the following example, only the first form will be used.
Type casting using a pointer to an object is illustrated in the following code:
TextBox display(Location(100,100), Shape(75, 50)); Number* Number = new Number(100); DisplayableNumber* number; Number->Next(); number = (DisplayableNumber*) Number; // type cast number->ShowIn(display); number->Show();
In this example, a Number object is accessed through two pointers: the first is of type Number* and the second is of type DisplayableNumber*. When accessed through the first pointer, the object is treated as a Number object (having a Next operation). When accessed through the second pointer, the object is treated as a DisplayableNumber object. The type cast is used to initialize the second pointer.
Type casting using references to an object is illustrated in the following code:
TextBox display(Location(100,100), Shape(75, 50)); Number number(100); number.Next(); DisplayableNumber& displayable = (Displayablenumber&) Number; // type cast displayable.ShowIn(display); displayable.Show();
In this example, a Number object is accessed through a variable of type Number (number) and a reference to DisplayableNumber (displayable). When accessed through the number variable, the object is treated as a Number object because that is the type of the variable. Thus, the Next() operation defined in the Number class can be applied to the object using the variable number. In other words, the variable number gives a Number class view of the object, a view that contains the Next method. When the same object is accessed via the variable displayable, it is treated as DisplayableNumber because that is the variable's type. Thus, the ShowIn and Show methods can be applied to the object identified by the variable displayable because these operations are defined in the DisplayableNumber class. Also notice that since object and references are used, the dot operator(.) is used to apply a method.
Methods can only be invoked when they are in the view defined by the type (or class) of the variable used to access the object. Examples of errors are:
Displayablenumber* numberPtr; Number *count1 = new Number(100); Number count2(200); numberPtr = (DisplayableNumber*)count1; DisplayableNumber& numberRef = (DisplayableNumber&) count2; numberPtr->Next(); // error numberRef.Next(); // error
Both of these accesses are in error because each attempts to use an operation that is not defined for the DisplayableNumber portion of the object. The object certainly has a Next method, but the view that is taken of that object (as a DisplayableNumber) does not reveal that aspect of it.
In this example, type casting is used to create a polymorphic NumberPanel class. The NumberPanel manages a collection of three objects of the DisplayableNumber class or any of its subclasses, while the NumberPanel class hides the existence of the TextBoxes used to display the objects and thus simplifies the management of several DisplayableNumber objects. Also, the NumberPanel defines a Show operation that will be applied to each of the objects it manages. The definition of the NumberPanel is shown below. For simplicity, the NumberPanel can only manage up to three objects.
The NumberPanel interface defines a method to Add another DisplayableNumber to the NumberPanel and to display its value in a given Frame when required by the Show method. The ShowIn method specifies the Frame where the NumberPanel is displayed. Notice that the DisplayableNumber added to a NumberPanel does not provide a TextBox in which its value is displayed - a TextBox is automatically supplied by the NumberPanel. The code for the NumberPanel is shown below.
The interesting aspect of the NumberPanel is that it is not restricted to a particular kind of DisplayableNumber. This means that it is not necessary to build several classes such as CounterPanel, CyclerPanel, or SwitchCounterPanel. The NumberPanel works for any single subclass of DisplayableNumber, or for any combinations of subclasses: a NumberPanel may contain two Number objects and one Cycler object, or one Number object, one Cycler object and one SwitchCounter object, for example. The NumberPanel can provide this level of generality because it is built to depend only on the methods defined in the DisplayableNumber class.
The following code shows the NumberPanel's flexibility:
Frame display(Location(100,100), Shape(200,200)); NumberPanel panel; Number number(100); Cycler octal(8); JumpCounter length(0); panel.ShowIn(display); // add different kinds of counters to the panel panel.Add((DisplayableNumber*)&number); panel.Add((DisplayableNumber*)&octal); panel.Add((DisplayableNumber*)&length); // manipulate individual counter objects number.Next(); octal.Next(); length.Next(50); // display all of the new values panel.Show();
In this code, the NumberPanel is managing three different kinds of DisplayableNumbers (a Number, a Cycler, and a JumpCounter). The type cast used in calling the NumberPanel::Add method, however, allows the NumberPanel object to treat them all as objects of their common base class (DisplayableNumber). This is possible because the NumberPanel uses only the methods defined in the base class.
Implict and Explict Type Casting
Type casting may be explicit or implicit. When explicit, the program contains code that directs the exact type casting that is done, as in
Number *number; DisplayableNumber *displayable = (DisplayableNumber*)number;
This is explicit type casting because the program code clearly states the casting from a derived type to a base type. Implicit type casting occurs when objects of different types are assigned or copied without an explicit type cast. For example, the assignment
Number *number; DisplayableNumber *displayable = counter;
is an implicit type cast.
Type casting commonly occurs in parameter passing. The formal parameter type is declared using the base type, while the type of the actual argument value is declared using a derived class. For example, the NumberPanel class has a method declared as
NumberPanel::Add(DisplayableNumber * dn);
This method might be called with either explicit or implicit type casting.
The NumberPanel::Add method is called with an explicit type cast in the following example:
NumberPanel panel; Number *n = new Number(100); panel.Add((DisplayableNumber*)n);
In this case, the formal parameter type is DisplayableNumber*, while the actual argument is of type Number*. On the call to the Add method, the actual argument is type cast to agree with the type of the formal parameter.
The following example uses an implicit type cast to invoke the NumberPanel::Add method:
NumberPanel panel; Number *c = new Number(100); panel.Add(c);
In this case, the Number* object is provided as an argument to a method defined to accept a DisplayableNumber* as its parameter. The example illustrates implicit type casting in that the program does not directly specify the type cast, but merely implies that one should be performed.
It is strongly recommended that implicit type casting be avoided so that the programmer's intent is clearly expressed in the code. The explicit type cast conveys visibly that the programmer was aware of the casting and deliberately chose to enact the cast. In the absence of an explicit type cast, other programmers (testers, maintainers, various members of the development team) cannot be certain about the programmer's intent. Is the absence of a type cast merely an oversight? or a mistake? Compilers will commonly issue warning messages when implicit type casts are performed.
The safety of implicit or explicit type casting is determined by whether the casting widens or narrows the type of the object. A type cast is said to widen the type of an object when a derived class object is type cast to a base class object. The term widen is used because the object type resulting from the cast applies to a wider collection of classes than the original type (class). For example, the type DisplayableNumber is a wider type than the type Number. A type cast is said to narrow the type type of an object when a base class is type cast to a derived class object. Narrow reflects that the type cast describes the object to a more limited class of objects. For example, type casting a DisplayableNumber object to a Cycler object makes the type of the object more determined.
A type cast that widens the type of an object is always safe, but a type cast the narrows the type of an object is dangerous. Widening an object's type is always safe because the object, through inheritance, has all of the methods of the wider type. For example, the type cast that widens a Number object to a DisplayableNumber type is safe because a Number object has all of the methods of the DisplayableNumber class. However, if a DisplayableNumber object is type cast to a class derived from DisplayableNumber, there can be no guarantee provided by the compiler that the operations on the resulting object are safe. Consider the following example:
DisplayableNumber *DNptr; Number number = new Number(100); Cycler *cycler; DNptr = (DisplayableNumber*)number; // safe; it widens cycler = (Cycler*)DNptr; // error cycler->Next(); // who knows what this will do!
The first type cast is safe; it widens the type of the number object from a Number class object to a DisplyableNumber object. The second type cast is unsafe and, in this example, is erroneous, because the narrowing type cast from DisplayableNumber to Cycler, while legal to the compiler, results in the variable cycler pointing to a Number object and not a Cycler object. The danger of this type of type casting becomes clearer in the last line when the Next method is applied to the object. Because the compiler believes (through the type casting explicitly insisted upon by the programmer) that the object is a Cycler object, the Cycler::Next method will be applied to an object that is actually a Number object. In the code for the Cycler::Next method, a reference is made to the base value of the object. However, the object in this case is a Number object and does not possess a base value. Clearly this situation is incorrect but, unfortunately, results in no compile-time warnings or errors.