Search

12.8 — Object slicing

Let’s go back to an example we looked at previously:

In the above example, ref references and ptr points to derived, which has a Base part, and a Derived part. Because ref and ptr are of type Base, ref and ptr can only see the Base part of derived -- the Derived part of derived still exists, but simply can’t be seen through ref or ptr. However, through use of virtual functions, we can access the most-derived version of a function. Consequently, the above program prints:

derived is a Derived and has value 5
ref is a Derived and has value 5
ptr is a Derived and has value 5

But what happens if instead of setting a Base reference or pointer to a Derived object, we simply assign a Derived object to a Base object?

Remember that derived has a Base part and a Derived part. When we assign a Derived object to a Base object, only the Base portion of the Derived object is copied. The Derived portion is not. In the example above, base receives a copy of the Base portion of derived, but not the Derived portion. That Derived portion has effectively been “sliced off”. Consequently, the assigning of a Derived class object to a Base class object is called object slicing (or slicing for short).

Because variable base does not have a Derived part, base.getName() resolves to Base::getName().

The above example prints:

base is a Base and has value 5

Used conscientiously, slicing can be benign. However, used improperly, slicing can cause unexpected results in quite a few different ways. Let’s examine some of those cases.

Slicing and functions

Now, you might think the above example is a bit silly. After all, why would you assign derived to base like that? You probably wouldn’t. However, slicing is much more likely to occur accidentally with functions.

Consider the following function:

This is a pretty simple function with a const base object parameter that is passed by value. If we call this function like such:

When you wrote this program, you may not have noticed that base is a value parameter, not a reference. Therefore, when called as printName(d), we might have expected base.getName() to call virtualized function getName() and print “I am a Derived”, that is not what happens. Instead, Derived object d is sliced and only the Base portion is copied into the base parameter. When base.getName() executes, even though the getName() function is virtualized, there’s no Derived portion of the class for it to resolve to. Consequently, this program prints:

In this case, it’s pretty obvious what happened, but if your functions don’t actually print any identifying information like this, tracking down the error can be challenging.

Of course, slicing here can all be easily avoided by making the function parameter a reference instead of a pass by value (yet another reason why passing classes by reference instead of value is a good idea).

This prints:

I am a Derived

Slicing vectors

Yet another area where new programmers run into trouble with slicing is trying to implement polymorphism with std::vector. Consider the following program:

This program compiles just fine. But when run, it prints:

I am a Base with value 5
I am a Base with value 6

Similar to the previous examples, because the std::vector was declared to be a vector of type Base, when Derived(6) was added to the vector, it was sliced.

Fixing this is a little more difficult. Many new programmers try creating a std::vector of references to an object, like this:

Unfortunately, this won’t compile. The elements of std::vector must be assignable, whereas references can’t be reassigned (only initialized).

One way to address this is to make a vector of pointers:

This prints:

I am a Base with value 5
I am a Derived with value 6

which works! But it’s quite a bit of additional headache since you now have to deal with dynamic memory allocation.

There’s one other way to resolve this. The standard library provides a useful workaround: the std::reference_wrapper class. Essentially, std::reference_wrapper is a class that acts like a reference, but also allows assignment and copying, so it’s compatible with std::vector.

The good news is that you don’t really need to understand how it works to use it. All you need to know are three things:
1) std::reference_wrapper lives in the <functional> header
2) When you create your std::reference_wrapper wrapped object, the object can’t be an anonymous object (since anonymous objects have expression scope would leave the reference dangling)
3) When you want to get your object back out of std::reference_wrapper, you use the get() member function.

Here’s our code rewritten to use std::reference_wrapper:

This works as you’d expect:

I am a Base with value 5
I am a Derived with value 6

and avoids having to deal with dynamic memory.

If this seems a bit obtuse or obscure at this point (especially the nested types), come back to it later after we’ve covered template classes and you’ll likely find it more understandable.

The Frankenobject

In the above examples, we’ve seen cases where slicing lead to the wrong result because the derived class had been sliced off. Now let’s take a look at another dangerous case where the derived object still exists!

Consider the following code:

The first three lines in the function are pretty straightforward. Create two Derived objects, and set a Base reference to the second one.

The fourth line is where things go astray. Since b points at d2, and we’re assigning d1 to b, you might think that the result would be that d1 would get copied into d2 -- and it would, if b were a Derived. But b is a Base, and the operator= that C++ provides for classes isn’t virtual by default. Consequently, only the Base portion of d1 is copied into d2.

As a result, you’ll discover that d2 now has the Base portion of d1 and the Derived portion of d2. In this particular example, that’s not a problem (because the Derived class has no data of its own), but in most cases, you’ll have just created a Frankenobject -- composed of parts of multiple objects. Worse, there’s no easy way to prevent this from happening (other than avoiding assignments like this as much as possible).

Conclusion

Although C++ supports assigning derived objects to base objects via object slicing, in general, this is likely to cause nothing but headaches, and you should generally try to avoid slicing. Make sure your function parameters are references (or pointers) and try to avoid any kind of pass-by-value when it comes to derived classes.

12.9 -- Dynamic casting
Index
12.7 -- Virtual base classes

20 comments to 12.8 — Object slicing

  • Panagiotis

    In the section "Slicing and functions" our function’s parameter is const Base &base. So, as we have already learnt, the compiler won’t let us call any functions that do not have the "const" keyword. I think that our function’s declaration should have been like

    for Base class, and

    for Derived class

  • nagarajsherigar

    Base base = derived;
    Assuming assignment operator is called
    wont derived *__vptr copied to base *__vptr?

  • Joe

    Hi Alex,

    Thanks a lot for your tutorials, they are tremendously helpful!

    I have a question which relates to both the concept of virtual functions and the object slicing. Consider the following classes:

    If I put animals of any flavour into m_animalsInStable, is there a way to call the intrinsic functions other than declaring them virtual in the Animal base class? To put it differently, within a member function of Stable can I do something like m_animalsInStable[0].get().getLitresOfMilkPerDay() ?

    • Alex

      You have two main options:
      1) Add a virtual function to the Animal base class, call that, and let it virtually resolve to the most derived class. This generally only makes sense for functions that apply to all Animals, but it’s nice because you don’t have to know what all the various derived classes are up-front.
      2) Dynamically cast your Animal into a derived object and call whatever function you want directly. The tricky part here is that you have to have some way to determine what derived class the Animal actually is.

      You _could_ create a virtual get() function that returns a pointer (or reference) to the derived object (so Cow would return a Cow* or Cow& to this or *this), but it doesn’t really buy you anything that you can’t do with a dynamic_cast anyway.

      One way to solve #2 would be to create an AnimalType enum. Then create a virtual getAnimalType() function in Animal. Each class can self-identify by returning the proper enum. You can switch on this enum to do whatever you need to do with a derived-class-specific function. e.g.

  • Hugh Mungus

    Hey Alex,

    Maybe I missed something, but can you explain why passing an object by value causes slicing and passing by reference doesn’t?

    • Alex

      When you pass an object by value, a copy of the object is made. The object’s type is used to determine what to copy. Thus, if your type is a base type, only the base part will be copied.

      With a pointer or reference, you’re not making a copy -- you’re passing a way of indirectly accessing the object. That indirection might only see the base part of the object, but the rest of the object is still there.

  • loveu

    Hi alex thanks again for replying so efficiently to my questions!

    for this example, std::vector creates an array of pointers which point to dynamically allocated pointers which point to dynamically allocated objects am I right? So the std::vector deals with the deallocation of dynamically allocated pointers and we have to seperately deal with dynamically allocated objects as you have done so with

    am I going in the right direction?

    • Alex

      Yes, you have it correct. There’s just a typo in your sentence, “std::vector creates an array of pointers which point to dynamically allocated pointers which point to dynamically allocated objects am I right”

      It should be: std::vector creates an array of pointers, each of which point to dynamically allocated objects.

  • David

    You explained Franken object by below lines:

        Derived d1(5);
        Derived d2(6);
        Base &b = d2;

        b = d1;

    Here in line b=d1; Base reference b is getting reassigned. Shouldn’t it throw compile error ??

    Thanks

    • Alex

      References can’t be reassigned. The are set upon initialization and thereafter any assignment to them is an assignment to the referenced object, not a reassignment of the reference itself.

      So in the quoted example, b = d1 means “assign d1 into the object b is referencing (which is d2)”. But since b is a Base, it only copies the Base potion of d1 into d2.

  • Jonas

    Hello, Alex! First off, thank you so much for these tutorials! I’ve been lurking for about a year. Great stuff!
    My question is what happens if I use pop_back() on the vector<std::reference_wrapper<object>>?
    Does it only remove the reference or does it call the object’s destructor as well?
    And furthermore, if the object was created using new, is there anything else I have to think about?
    I tried using delete on both the the wrapper and the object (using .get()) but the compiler wouldn’t let me, obviously
    Cheers.

    • Alex

      Calling pop_back on a reference wrapped element just removes the reference element from the array, it doesn’t call any destructors. If the object was created using new, then you’ll need to manually destroy it. But if you’re creating objects using new, then you should probably be using a std::vector of pointers rather than reference_wrapper.

  • Saiyu

    I didn’t notice this before, thank the author!

Leave a Comment

Put C++ code inside [code][/code] tags to use the syntax highlighter