C++ Object Slicing: Value Copies, Polymorphism, and Fixes

C++ Object Slicing: Value Copies, Polymorphism, and Fixes

이 글의 핵심

Copying or passing derived objects by base value slices away derived members and breaks polymorphism; fix with references, pointers, and smart-pointer containers.

What is object slicing?

When you put a derived object into a base object by value, only the base subobject is copied; members and polymorphic behavior that belong only to Derived are cut off—like slicing a loaf of bread.

Why it happens (memory view)

In Base b = d;, the left-hand side is type Base, so the compiler only allocates sizeof(Base). The assignment copies the base part of d. Fields that exist only in Derived (e.g. y below) have no storage in b.

class Base {
    int x;   // part of Base layout
};

class Derived : public Base {
    int y;   // extra member after Base
};

Derived d;
d.x = 1;
d.y = 2;

Base b = d;  // only x is meaningfully copied; y is sliced
// b’s static type is Base, so b.y is ill-formed

Why virtual dispatch “breaks” follows the same idea: by value, the live object is a base-sized copy, so calls may resolve like the base type (see example 1).

Causes

// 1. Pass by value
void func(Base b) {  // slicing
    // ...
}
Derived d;
func(d);

// 2. Return by value
Base func() {
    Derived d;
    return d;  // slicing
}

// 3. Containers
std::vector<Base> vec;
Derived d;
vec.push_back(d);  // slicing

Practical examples

Example 1: The bug

class Animal {
public:
    virtual void speak() {
        std::cout << "Animal" << std::endl;
    }
};

class Dog : public Animal {
public:
    void speak() override {
        std::cout << "Woof!" << std::endl;
    }
};

void makeSpeak(Animal a) {  // by value
    a.speak();  // always "Animal"
}

int main() {
    Dog d;
    makeSpeak(d);  // "Animal" (slicing)
}

Example 2: Correct fix

// Reference
void makeSpeak(Animal& a) {
    a.speak();  // polymorphism works
}

// Pointer
void makeSpeak(Animal* a) {
    a->speak();  // polymorphism works
}

int main() {
    Dog d;
    makeSpeak(d);   // "Woof!"
    makeSpeak(&d);  // "Woof!"
}

Example 3: Containers

// Bad: value container
std::vector<Animal> animals;
Dog d;
animals.push_back(d);  // slicing
animals[0].speak();    // "Animal"

// Pointer container
std::vector<Animal*> animals;
Dog* d = new Dog();
animals.push_back(d);
animals[0]->speak();   // "Woof!"

// Smart pointers
std::vector<std::unique_ptr<Animal>> animals;
animals.push_back(std::make_unique<Dog>());
animals[0]->speak();   // "Woof!"

Example 4: Preventing copy

class Base {
public:
    virtual ~Base() = default;
    
    Base(const Base&) = delete;
    Base& operator=(const Base&) = delete;
    
    Base(Base&&) = default;
    Base& operator=(Base&&) = default;
    
protected:
    Base() = default;
};

Common pitfalls

Pitfall 1: Pass by value

// Bad
void process(Base obj) {
    obj.virtualFunc();  // slicing
}

// Good
void process(const Base& obj) {
    obj.virtualFunc();  // polymorphism
}

Pitfall 2: Return by value

// Bad
Base create() {
    return Derived();  // slicing
}

// Good
std::unique_ptr<Base> create() {
    return std::make_unique<Derived>();
}

Pitfall 3: Assignment

Derived d;
Base b;
b = d;  // slicing

// Pointer keeps dynamic type
Base* b = &d;  // polymorphism preserved

Pitfall 4: Storing in vectors by value

// Bad
std::vector<Base> vec;
vec.push_back(Derived());  // slicing

// Good
std::vector<std::unique_ptr<Base>> vec;
vec.push_back(std::make_unique<Derived>());

Detection

// Compiler warnings
g++ -Weffc++ program.cpp

// Delete copy ctor
class Base {
public:
    Base() = default;
    Base(const Base&) = delete;  // prevent slicing
};

FAQ

Q1: When does slicing happen?

A: When a derived object is copied into a base by value.

Q2: Fixes?

A:

  • References
  • Pointers
  • Smart pointers

Q3: How to detect?

A:

  • Compiler warnings
  • Deleted copy operations

Q4: Performance?

A: References and pointers add no slicing issue.

Q5: Containers?

A: Prefer smart-pointer containers for polymorphic types.

Q6: Learning resources?

A:

  • Effective C++
  • C++ Primer
  • More Effective C++

  • Inheritance and polymorphism
  • Virtual functions
  • Smart pointers

Practical tips

Debugging

  • Check warnings first
  • Reproduce minimally

Performance

  • Profile before tuning

Code review

  • Follow conventions

Practical checklist

Before coding

  • Right technique for the problem?
  • Team can maintain it?
  • Meets performance goals?

While coding

  • Warnings clean?
  • Edge cases?
  • Error handling?

At review

  • Intent clear?
  • Tests enough?
  • Documented?

Keywords

C++, object slicing, polymorphism, inheritance, value semantics


  • Virtual functions
  • CRTP pattern
  • CRTP guide
  • Decorator pattern
  • Virtual destructor