Skip to main content

Section 19.8 Object Slicing and Dynamic Casting

Subsection 19.8.1 Object Slicing

The makeIntroduction function that we used to demonstrate polymorphism took a const Person& parameter. As discussed earlier, it is always a good idea to take complex objects by reference to avoid making temporary copies of them. However, when trying to get polymorphic behavior, working with references or pointers is essential.
To understand why, let’s consider what happens if this code runs:
Student s("Alex", 20, "Computer Science");
Person p = s;
The second line says β€œMake a new Person object called p and make it be a copy of the Student object s.” The resulting object p will just be a Person. That means there will only be storage for the things we need for a plain Person. There is no room to store anything extra provided by Student. So that data from s will be β€œsliced” off as the copy is made. This is known as object slicing.
Person p has no space to store a m_major variable even though it is a copy of Student s that has one.
Figure 19.8.1. Student s β€œsliced” as it is copied into p.
Thus if we rewrote the makeIntroduction function to use pass by value, it would no longer be able to produce polymorphic behavior. The object we are working with is just a Person and will never have extra information.
Listing 19.8.2.
void makeIntroduction(Person person) {
    // Person is a COPY of the object passed in - any extra data was sliced off
    person.introduce();  // Can only do Person::introduce()
    // Consider using a reference to avoid slicing
}
The original makeIntroduction uses pass by const reference, so the person parameter is not a copy of what is passed in, it is a reference to the original. Nothing needed to be sliced. That means at run time we can check β€œWhat kind of object is this? Which version of introduce should we use?”
person is a reference to the Student s. It thus has access to all the student information.
Figure 19.8.3. When person parameter is a reference, it retains access to all the data from the original object.
The same basic idea applies to pointers. If we had a pointer p to a Person object, and the object it points to happens to be a Student, using p->introduce() will dynamically find the version of introduce() that is most appropriate for the actual object type.

Insight 19.8.1.

Object slicing is one of those things you need to understand not so you can do it, but so that you know to avoid doing it.
You will not normally want to slice objects. Make sure to take object parameters by reference to avoid slicing.

Checkpoint 19.8.1.

Manager inherits from Employee. m is a Manager object. What is the result of Employee e = m;
  • A new Employee object that copies just the Employee part of the Manager.
  • A new Employee object that copies the entire Manager object.
  • Incorrect. The Employee part of a Manager is not the entire Manager.
  • A compile error because you can’t copy a Manager into an Employee.
  • Incorrect. You can copy a Manager into an Employee, but it will slice off the extra data.
  • A new Manager object that is stored in e.
  • Incorrect. e only has space to store an Employee.

Checkpoint 19.8.2.

Manager inherits from Employee. m is a Manager object. What is the result of Employee& e = m;
  • A reference to the Manager object. The full object is still there.
  • A reference to the Manager object. Everything but the Employee part is removed from the object.
  • Incorrect. The reference does not change the object it refers to.
  • A compile error because an Employee reference can’t refer to a Manager.
  • Incorrect. Manager is-a Employee. So an Employee reference can refer to a Manager.

Checkpoint 19.8.3.

Manager inherits from Employee. e is an Employee object. What is the result of Manager m = e;
  • A new Manager object that copies just the Manager part of the Employee.
  • A new Manager object that copies just the Manager part of the Employee.
  • A compile error because you can’t copy an Employee into a Manager.
  • Correct. An Employee doesn’t have all the data needed to make a Manager.
  • A new Employee object that is stored in m.
  • Incorrect. m must store a full Manager. Employee is not a Manager.

Subsection 19.8.2 Dynamic Casting

We can always take a derived class and refer to it as if it were an instance of the base class because it has an is-a relationship.
Student s("Alex", 20, "Computer Science");
Person& pref = s;  // pref is a reference to a Person that happens to be a Student
Person* pPtr = &s;  // pPtr is a pointer to a Person that happens to be a Student
Person p = s;  // p is a copy of the Student object, but only the Person part
In contrast, we are not allowed to take a more general base class and treat it as a derived class. For plain objects, this is entirely logical. We can’t treat a plain Person as a Studentβ€”a plain Person does not have a m_major and thus can’t do anything specific to a Student.
Person p("Wendy", 30);
Student& sRef = p;  // Error! A Student & can't refer to a plain Person
Student* sPtr = &p;  // Error! A Student* can't point to a plain Person
Student s = p;  // Error! Can't copy a Person into a Student
However, if we have a pointer to a base class, it might point to a derived object. And we might need to access the extra information of that class. Imagine that Student has a method getMajor. And our code has been given p which is a Person*. We would like to check if that Person* actually points at a Student and, if so, call getMajor() on it. We can’t just write:
p->getMajor()
All the compiler knows for sure is that p has the memory address of some Person. It doesn’t know if it actually points to a Student. If it points to a plain Person object, there is no getMajor() function to make use of. So it refuses to compile that code.
But we can use a dynamic cast (remember that β€œdynamic” means β€œat runtime”) to attempt to create a new pointer of type Student*. The syntax for doing so is:
Student* sPtr = dynamic_cast<Student*>(p);
We are asking the compiler to take the Person* p and examine it. If it does point to a Student object, we will store the memory address into the new variable sPtr. p and sPtr will store the same memory address and point at the same object. But because sPtr is a pointer to a Student the compiler can be confident that it points to a Student and it is safe to do things like call getMajor() on it.
Both p and sPtr hold the memory address of the same Student.
Figure 19.8.4. p and sPtr both point at the same Student. sPtr is known to be pointing at a Student. We don’t know what type of object p is pointing to.
If the check fails because p is not in fact pointing at a Student, the dynamic_cast will result in a nullptr. So, before we use the pointer produced by dynamic_cast, we should check to see if it is null or not:
Listing 19.8.5.
Student* sPtr = dynamic_cast<Student*>(p);
if (sPtr) {  // or if (sPtr != nullptr)
    // dynamic_cast worked - sPtr is known to point to a Student
    sPtr->getMajor();  
} else {
    // dynamic_cast failed - p was not a Student
    cout << "p is not a Student" << endl;
}
Usually, polymorphism is a better way to make a derived class behave differently than its parent. When we used the introduce() function, we did not need to check if the object was a Student or not. We just called introduce() and the right version was used. But there are times when you absolutely need to know β€œIs this pointer pointing at a derived class?”. dynamic_cast<DerivedClass*>(pointer) is the way to check.

Checkpoint 19.8.4.

What best describes a dynamic cast?
  • A way to attempt making a pointer to a derived class from a pointer to a base class.
  • A way to attempt making a pointer to a base class from a pointer to a derived class.
  • Incorrect. Going from derived to base class is upcasting - that happens automatically because it is always safe.
  • A way to change a pointer to a new type.
  • dynamic_cast can only be used with inherited classes.

Checkpoint 19.8.5.

We have a pointer ep that points to an object of type Employee. Construct a line to try making a Manager pointer mp from ep.
You have attempted of activities on this page.