15.3. Conversions and Inheritance
As we've seen, every derived object contains a base part, which means that we can execute operations on a derived object as if it were a base object. Because a derived object is also a base, there is an automatic conversion from a reference to a derived type to a reference to its base type(s). That is, we can convert a reference to a derived object to a reference to its base subobject and likewise for pointers. Base-type objects can exist either as independent objects or as part of a derived object. Therefore, a base object might or might not be part of a derived object. As a result, there is no (automatic) conversion from reference (or pointer) to base to reference (or pointer) to derived. The situation with respect to conversions of objects (as opposed to references or pointers) is more complicated. Although we can usually use an object of a derived type to initialize or assign an object of the base type, there is no direct conversion from an object of a derived type to an object of the base type. 15.3.1. Derived-to-Base ConversionsIf we have an object of a derived type, we can use its address to assign or initialize a pointer to the base type. Similarly, we can use a reference or object of the derived type to initialize a reference to the base type. Pedantically speaking, there is no similar conversion for objects. The compiler will not automatically convert an object of derived type into an object of the base type. It is, however, usually possible to use a derived-type object to initialize or assign an object of base type. The difference between initializing and/or assigning an object and the automatic conversion that is possible for a reference or pointer is subtle and must be well understood. Conversion to a Reference is Not the Same as Converting an ObjectAs we've seen, we can pass an object of derived type to a function expecting a reference to base. We might therefore think that the object is converted. However, that is not what happens. When we pass an object to a function expecting a reference, the reference is bound directly to that object. Although it appears that we are passing an object, the argument is actually a reference to that object. The object itself is not copied and the conversion doesn't change the derived-type object in any way. It remains a derived-type object. When we pass a derived object to a function expecting a base-type object (as opposed to a reference) the situation is quite different. In that case, the parameter's type is fixedboth at compile time and run time it will be a base-type object. If we call such a function with a derived-type object, then the base-class portion of that derived object is copied into the parameter. It is important to understand the difference between converting a derived object to a base-type reference and using a derived object to initialize or assign to a base-type object. Using a Derived Object to Initialize or Assign a Base ObjectWhen we initialize or assign an object of base type, we are actually calling a function: When we initialize, we're calling a constructor; when we assign, we're calling an assignment operator. When we use a derived-type object to initialize or assign a base object, there are two possibilities. The first (albeit unlikely) possibility is that the base class might explicitly define what it means to copy or assign an object of the derived type to an object of the base type. It would do so by defining an appropriate constructor or assignment operator: class Derived; class Base { public: Base(const Derived&); // create a new Base from a Derived Base &operator=(const Derived&); // assign from a Derived // ... }; In this case, the definition of these members would control what happens when a Derived object is used to initialize or assign to a Base object. However, it is uncommon for classes to define explicitly how to initialize or assign an object of the base type from an object of derived type. Instead, base classes ususally define (either explicitly or implicitly) their own copy constructor and assignment operator (Chapter 13). These members take a parameter that is a (const) reference to the base type. Because there is a conversion from reference to derived to reference to base, these copy-control members can be used to initialize or assign a base object from a derived object: Item_base item; // object of base type Bulk_item bulk; // object of derived type // ok: uses Item_base::Item_base(const Item_base&) constructor Item_base item(bulk); // bulk is "sliced down" to its Item_base portion // ok: calls Item_base::operator=(const Item_base&) item = bulk; // bulk is "sliced down" to its Item_base portion When we call the Item_base copy constructor or assignment operator on an object of type Bulk_item, the following steps happen:
In these cases, we say that the Bulk_item portion of bulk is "sliced down" as part of the initialization or assignment to item. An Item_base object contains only the members defined in the base class. It does not contain the members defined by any of its derived types. There is no room in an Item_base object for the derived members. Accessibility of Derived-to-Base ConversionLike an inherited member function, the conversion from derived to base may or may not be accessible. Whether the conversion is accessible depends on the access label specified on the derived class' derivation.
If the inheritance is public, then both user code and member functions of subsequently derived classes may use the derived-to-base conversion. If a class is derived using private or protected inheritance, then user code may not convert an object of derived type to a base type object. If the inheritance is private, then classes derived from the privately inherited class may not convert to the base class. If the inheritance is protected, then the members of subsequently derived classes may convert to the base type. Regardless of the derivation access label, a public member of the base class is accessible to the derived class itself. Therefore, the derived-to-base conversion is always accessible to the members and friends of the derived class itself. 15.3.2. Conversions from Base to DerivedThere is no automatic conversion from the base class to a derived class. We cannot use a base object when a derived object is required: Item_base base; Bulk_item* bulkP = &base; // error: can't convert base to derived Bulk_item& bulkRef = base; // error: can't convert base to derived Bulk_item bulk = base; // error: can't convert base to derived The reason that there is no (automatic) conversion from base type to derived type is that a base object might be just thata base. It does not contain the members of the derived type. If we were allowed to assign a base object to a derived type, then we might attempt to use that derived object to access members that do not exist. What is sometimes a bit more surprising is that the restriction on converting from base to derived exists even when a base pointer or reference is actually bound to a derived object: Bulk_item bulk; Item_base *itemP = &bulk; // ok: dynamic type is Bulk_item Bulk_item *bulkP = itemP; // error: can't convert base to derived The compiler has no way to know at compile time that a specific conversion will actually be safe at run time. The compiler looks only at the static types of the pointer or reference to determine whether a conversion is legal. In those cases when we know that the conversion from base to derived is safe, we can use a static_cast (Section 5.12.4, p. 183) to override the compiler. Alternatively, we could request a conversion that is checked at run time by using a dynamic_cast, which is covered in Section 18.2.1 (p. 773). ![]() |