Object slicing in C++

What slicing is

Object slicing occurs when you assign derived class objects using base class variables. The result of this is that the derived class part of an object can be “sliced off” and lost.

Consider the following code, where Derived is derived from Base:

Derived d;
Base b = d;

This assignment is perfectly legal, since a Derived is implicitly convertible to a Base, but since a Base variable is only big enough to hold a Base object, any data members that Derived adds will be sliced off, leaving just its Base part.

In this case, the slicing is not a problem. A base class variable can only be used to access base class members, so the derived class part will not be needed. If any base class nonvirtual functions call virtual functions, the base class implementation will be used (remember that it wouldn’t be possible to declare a base class variable if the base class had any pure virtual functions).

When it can be a problem

Slicing can become a problem, however, when you assign derived class objects through references to the base class. Consider the following program:

#include <iostream>

struct Base
{
    Base(int b)
        : b_(b)
    {
    }
    int b_;
};

struct Derived : Base
{
    Derived(int b, int d)
        : Base(b), d_(d)
    {
    }
    int d_;
};

int main()
{
    Derived d1(1, 1);
    Derived d2(2, 2);
    Base& b = d1;
    b = d2;
    std::cout << "d1: " << d1.b_ << d1.d_ << "\n";
    std::cout << "d2: " << d2.b_ << d2.d_ << "\n";
}

First, a base class reference b is made to a derived class object d1. This does not cause slicing because it is just the creation of a reference. Next, another derived class object d2 is assigned to the same reference. Recall that assigning to a reference overwrites the referent. This means that d2 now overwrites d1. But there is a problem: because the reference is to Base, only the Base part of d2 overwrites d1. The Derived part of d1 remains unaffected, so you end up with a hybrid of d1 and d2.

This is the actual output on running the program. Note that d1 now has d2‘s base class part, but it still has its own derived class part:

d1: 21
d2: 22

A solution

It is possible to prevent this kind of slicing. If you implement a base class assignment operator that calls a virtual function assign() to do the actual assignment, you can have the derived class override check the class of the other object, and if it is of the same class, perform the assignment of the derived class part.

struct Base
{
    Base(int b)
        : b_(b)
    {
    }
    Base& operator=(const Base& other)
    {
        b_ = other.b_;
        return assign(other);
    }
    virtual Base& assign(const Base& other)
    {
        return *this;
    }
    int b_;
};

struct Derived : Base
{
    Derived(int b, int d)
        : Base(b), d_(d)
    {
    }
    virtual Derived& assign(const Base& other)
    {
        const Derived& other_derived = dynamic_cast<const Derived&>(other);
        d_ = other_derived.d_;
        return *this;
    }
    int d_;
};

Note that the base class assign() method will only be called when assigning to a base class object, not a reference. It just returns *this because the actual assignment has already happened in the assignment operator. If I had put the actual assignment in the assign() method it would have required all subclasses to call it explicitly in their own assign() override.

Note also that the dynamic_cast will throw a bad_cast if other isn’t a Derived.

The output of the program with these changes is as expected; d1 is completely overwritten by d2:

d1: 22
d2: 22