Inheritance and Polymorphism

Object Slicing

Object slicing can occur when a derived class object is copied or assigned into a base class object by value. What happens here is that only the base class subobject of the derived object is copied; any additional members and the actual dynamic type associated with the derived object are dropped, so what you get after copy or assignment is effectively just a regular base class object.

This effect is easy to see with a trivial class hierarchy. The base class stores a single integer and a virtual function to print it:

#include <iostream>

class Base
{
public:
    Base(int value = 0)
        : m_value{ value }
    {
    }

    // Run this destructor when the object lifetime ends
    virtual ~Base()
    {
    }

    virtual void print() const
    {
        std::cout << "Base value " << m_value;
    }

protected:
    int m_value;
};

The derived class adds a second integer and overrides the virtual function:

class Derived : public Base
{
public:
    Derived(int value = 0, int extra = 0)
        : Base{ value }
        , m_extra{ extra }
    {
    }

    // Mark this function as an override of a virtual base function
    void print() const override
    {
        std::cout << "Derived value " << m_value << " extra " << m_extra;
    }

private:
    int m_extra;
};

When a Derived object is passed by value to a function expecting a Base, it is sliced:

void slice(Base b)
{
    b.print();
    std::cout << "\n";
}

int main()
{
    Derived d{ 10, 99 };

    slice(d); // slicing occurs here

    return 0;
}

The parameter b in the slice function is a brand new object of type Base. It is initialized from d through the Base copy constructor, which only copies over the members belonging to the base subobject, namely m_value. The Derived specific members are irrelevant here, since their containing class Derived is no longer represented by the parameter. After the call, the static and dynamic type of b will both be Base, so the call b.print() invokes Base::print(), which produced:

Base value 10

The same behavior occurs during regular assignment:

Derived d{ 10, 99 };

Base b{ d }; // slicing occurs here

b.print();
std::cout << "\n";

Here, the assignment operator of Base effectively takes the base subobject of d and copies it over to b. After the assignment b is a pure Base object with a Base::print() invoked, producing the same output:

Base value 10

You can avoid slicing by passing by reference or by pointer. A reference parameter does not create a copy of the original object; it just aliases the original object:

// Bind by reference without allowing modification through it
void notSliced(const Base& b) // note const reference
{
    b.print();
    std::cout << "\n";
}

int main()
{
    Derived d{ 10, 99 };

    notSliced(d); // notSliced gets a reference to d

    return 0;
}

Since b is a reference bound to the original Derived object, the call to b.print() actually invokes Derived::print() due to polymorphism, which produces:

Derived value 10 extra 99

As you can see, the derived members and static type are still present and correctly invoked. Similar results occur for pointer arguments.

 Object slicing happens to inherited objects passed by value to base class or sibling class parameters or objects. This is because copy constructors, assignment operators, and normal function parameters that pass by value construct a new object by calling implicitly a copy constructor that only knows about the parts inherited from the passed in objects.

Exercise: Object SlicingStarter code, hints and solutionOpenClose
Guided practiceBeginner · 10–15 min

Practice: Object Slicing

Work through the mission, reveal hints only when needed, and compare your result with the model solution.

Your challenge

Create Derived other{4,8} and call notSliced(other). Predict the output and explain how the reference parameter preserves the Derived object.

Mission checklist

  1. Run the starter as a baseline, then predict the change requested in Your challenge.
  2. Make that change in your editor; use the lesson-specific hints if you need help.
  3. Compile and run your answer, then compare with the worked solution and explain the result.

Starter program

This is the working baseline, not the completed answer. Compile as C++20, then make the change described above.

#include <iostream>

class Base
{
public:
    Base(int value = 0)
        : m_value{ value }
    {
    }

    // Run this destructor when the object lifetime ends
    virtual ~Base()
    {
    }

    virtual void print() const
    {
        std::cout << "Base value " << m_value;
    }

protected:
    int m_value;
};

class Derived : public Base
{
public:
    Derived(int value = 0, int extra = 0)
        : Base{ value }
        , m_extra{ extra }
    {
    }

    // Mark this function as an override of a virtual base function
    void print() const override
    {
        std::cout << "Derived value " << m_value << " extra " << m_extra;
    }

private:
    int m_extra;
};

// Bind by reference without allowing modification through it
void notSliced(const Base& b) // note const reference
{
    b.print();
    std::cout << "\n";
}

int main()
{
    Derived d{ 10, 99 };

    notSliced(d); // notSliced gets a reference to d

    return 0;
}

Hints

Hint 1

notSliced takes const Base& instead of a Base value. It binds to the base part of the existing Derived object without constructing a sliced Base copy.

Hint 2

Check that print is virtual if you expect a Derived override through that reference. Passing by reference avoids slicing, but virtual dispatch is a separate requirement.

Worked solution

The new Derived contains base value 4 and extra value 8. Passing it to notSliced binds a reference instead of constructing a Base copy, so virtual dispatch prints both values from the Derived object.

#include <iostream>

class Base
{
public:
    Base(int value = 0)
        : m_value{ value }
    {
    }

    // Run this destructor when the object lifetime ends
    virtual ~Base()
    {
    }

    virtual void print() const
    {
        std::cout << "Base value " << m_value;
    }

protected:
    int m_value;
};

class Derived : public Base
{
public:
    Derived(int value = 0, int extra = 0)
        : Base{ value }
        , m_extra{ extra }
    {
    }

    // Mark this function as an override of a virtual base function
    void print() const override
    {
        std::cout << "Derived value " << m_value << " extra " << m_extra;
    }

private:
    int m_extra;
};

// Bind by reference without allowing modification through it
void notSliced(const Base& b) // note const reference
{
    b.print();
    std::cout << "\n";
}

int main()
{
    Derived d{ 10, 99 };

    notSliced(d); // notSliced gets a reference to d

    Derived other{4, 8};
    notSliced(other);
    return 0;
}

Example output

Derived value 10 extra 99
Derived value 4 extra 8

0 comments

No comments yet. Be the first.

Add a comment

About CppLearn

CppLearn is an interactive, free reference for modern C++. In each lesson, you'll find out how the language works, rather than just lines of code that you can copy and paste. We cover concepts starting from beginner lessons on fundamentals and proceed to templates, move semantics, smart pointers, and areas that usually trap or scare C++ learners.