I first saw the term "upcast" in Thinking in C++, by Bruce Eckel, 1995 (Prentice Hall). Although the term does not appear in every C++ text, I continue to use and expand on it because it succinctly and accurately describes a critical concept.
Converting a value from one data type to another is called typecasting or casting for short. Casting an object from one class type to another makes sense when the original classy type is a subclass of the destination type. For example, if Circle is a subclass of Shape (see Figure 4 below), a Circle "is a" Shape, so it makes sense to think of a Circle as a Shape or to convert an instance of Circle to an instance of Shape. However, it doesn't make sense to convert an Employee to Shape. Although we didn't call it "upcasting" at the time, we've used this operation previously.
Upcasting
Upcasting, as its name suggests, combines two concepts. First, a casting operation converts a value represented by one data type to an appropriate value represented by a different type. Second, when we cast objects, we always do so in the context of inheritance. If we arrange the inheritance hierarchy with each superclass above its subclasses, casting from a subclass to its superclass always takes place upwards in an inheritance hierarchy. (However, you should keep in mind that as UML class diagrams become large and complex, it's not always possible to place superclasses above their subclasses; we use "upwards" to mean "in the direction of the inheritance arrows.")
1. D → A
2. D → B
3. D → C
4. C → A
5. C → B
6. B → A
(a)
(b)
Upcasting options. Programmers may upcast an object to any convenient superclass.
An inheritance hierarchy with four classes arranged in four levels. Class A is a superclass to classes B, C, and D. Classes B and C are both a superclass and a subclass. Class D is only a subclass.
Upcasting can occur anywhere along an inheritance hierarchy, beginning and ending at any level. Six different upcasts are possible in this example.
D my_d;
A* a_ptr = &D;
A* a_ptr = new D;
(a)
(b)
Upcasting with assignment. Programmers perform a simple upcast with an assignment operation, either taking the address of an existing object or creating a new object. Both examples convert an instance of class D (on the right-hand side of the assignment operator) to an instance of class A (on the left-hand side). Notice that casting takes place automatically, without an explicit cast operator, making an upcast similar to a type promotion. It's easy to spot upcasting when implemented as an assignment operation.
Taking the address of an existing object with the address-of operator and assigning it to a superclass pointer
Dynamically creating a new object and assigning the address to a superclass pointer
In actual programs, upcasting is not as overt as in the above examples. Upcasting more commonly arises from function calls when a program passes an instance of a subclass to a function parameter of superclass type. It's far more difficult to recognize upcasting implemented this way.
void render(Shape* s)
{
.
.
.
}
.
.
.
Circle* c = new Circle;
render(c);
(a)
(b)
Upcasting by calling a function.
Programs rarely perform an upcast operation with a simple assignment statement; they typically perform an upcast by passing an instance of a child class by-pointer to a function that "expects" an instance of a parent class. (a) illustrates a simple inheritance hierarchy while (b) demonstrates how the function call results in an upcast. The result of the function call has the same affect as the assignment statement Shape* s = new Circle;. The ability to replace or substitute an instance of a subclass (in the function call) for an instance of a superclass (in the function definition) is an important feature of inheritance called Substitutability.
Upcasting vs. Downcasting
It is also possible and sometimes necessary to cast "downwards" in an inheritance hierarchy. (Recall that it's not always possible to place superclasses above their subclasses, so we use "downwards" to mean "in the opposite direction of the inheritance arrows.") However, downcasting is somewhat riskier than upcasting and requires an explicit cast operation. So, upcasting is safe, and the compiler does it automatically, but downcasting is risky, and the compiler makes us explicitly perform the downcast. Chapter 2 introduced the casting operator and we extend it to classes and objects here.