C++ 슬라이싱 문제 | "객체가 잘렸어요" 상속 복사 에러 해결
이 글의 핵심
C++ 슬라이싱 문제에 대한 실전 가이드입니다.
들어가며: “파생 클래스를 복사했더니 데이터가 사라졌어요"
"다형성이 작동하지 않아요”
C++에서 파생 클래스 객체를 베이스 클래스 타입으로 복사하면, 파생 클래스의 멤버가 잘려나가는 슬라이싱(Slicing) 문제가 발생합니다.
// ❌ 슬라이싱 문제
class Animal {
public:
virtual void speak() {
std::cout << "Animal sound\n";
}
};
class Dog : public Animal {
std::string name_;
public:
Dog(const std::string& name) : name_(name) {}
void speak() override {
std::cout << name_ << " barks\n";
}
};
void makeSound(Animal animal) { // ❌ 값 전달
animal.speak();
}
int main() {
Dog dog("Buddy");
makeSound(dog); // ❌ 슬라이싱 발생
// 출력: Animal sound (Dog가 잘림!)
}
이 글에서 다루는 것:
- 슬라이싱 문제란?
- 다형성 손실
- 참조·포인터로 해결
- 복사 방지 패턴
목차
1. 슬라이싱 문제란?
슬라이싱 발생
class Base {
public:
int x = 1;
virtual void foo() {
std::cout << "Base::foo\n";
}
};
class Derived : public Base {
public:
int y = 2; // 파생 클래스 멤버
void foo() override {
std::cout << "Derived::foo\n";
}
};
int main() {
Derived d;
Base b = d; // ❌ 슬라이싱 발생
std::cout << b.x << '\n'; // 1
// std::cout << b.y << '\n'; // 컴파일 에러: y가 없음
b.foo(); // Base::foo (다형성 손실)
}
문제:
y멤버 손실- 다형성 손실 (virtual 무시)
- vtable 포인터 복사 안 됨
2. 다형성 손실
예시 1: 함수 인자
// ❌ 값 전달
void process(Animal animal) { // 슬라이싱
animal.speak(); // Animal::speak 호출
}
Dog dog("Buddy");
process(dog); // Animal sound (다형성 손실)
// ✅ 참조 전달
void process(Animal& animal) { // 슬라이싱 없음
animal.speak(); // Dog::speak 호출
}
process(dog); // Buddy barks (다형성 유지)
예시 2: 컨테이너
// ❌ vector<Base>
std::vector<Animal> animals;
animals.push_back(Dog("Buddy")); // 슬라이싱
animals[0].speak(); // Animal sound
// ✅ vector<unique_ptr<Base>>
std::vector<std::unique_ptr<Animal>> animals;
animals.push_back(std::make_unique<Dog>("Buddy"));
animals[0]->speak(); // Buddy barks
3. 해결책: 참조·포인터
해결책 1: 참조 사용
// ✅ 참조
void process(const Animal& animal) {
animal.speak();
}
Dog dog("Buddy");
process(dog); // Buddy barks
해결책 2: 포인터 사용
// ✅ 포인터
void process(Animal* animal) {
if (animal) {
animal->speak();
}
}
Dog dog("Buddy");
process(&dog); // Buddy barks
해결책 3: 스마트 포인터
// ✅ unique_ptr
void process(std::unique_ptr<Animal> animal) {
animal->speak();
}
process(std::make_unique<Dog>("Buddy")); // Buddy barks
// ✅ shared_ptr
void process(std::shared_ptr<Animal> animal) {
animal->speak();
}
process(std::make_shared<Dog>("Buddy")); // Buddy barks
4. 복사 방지 패턴
패턴 1: 복사 생성자 delete
class Animal {
public:
Animal() = default;
Animal(const Animal&) = delete; // 복사 금지
Animal& operator=(const Animal&) = delete;
virtual ~Animal() = default;
virtual void speak() = 0;
};
class Dog : public Animal {
public:
void speak() override {
std::cout << "Bark\n";
}
};
int main() {
Dog dog;
// Animal animal = dog; // 컴파일 에러
Animal& ref = dog; // OK
}
패턴 2: protected 복사 생성자
class Animal {
protected:
Animal(const Animal&) = default; // 파생 클래스만 복사 가능
public:
Animal() = default;
virtual ~Animal() = default;
virtual void speak() = 0;
};
int main() {
Dog dog1;
// Animal animal = dog1; // 컴파일 에러
Dog dog2 = dog1; // OK (파생 클래스 복사)
}
패턴 3: clone 메서드
class Animal {
public:
virtual ~Animal() = default;
virtual std::unique_ptr<Animal> clone() const = 0;
virtual void speak() = 0;
};
class Dog : public Animal {
std::string name_;
public:
Dog(const std::string& name) : name_(name) {}
std::unique_ptr<Animal> clone() const override {
return std::make_unique<Dog>(*this);
}
void speak() override {
std::cout << name_ << " barks\n";
}
};
int main() {
Dog dog("Buddy");
std::unique_ptr<Animal> cloned = dog.clone();
cloned->speak(); // Buddy barks
}
정리
슬라이싱 방지 규칙
| 방법 | 장점 | 단점 |
|---|---|---|
| 참조 | 간단, 다형성 유지 | null 불가 |
| 포인터 | null 가능, 다형성 유지 | 수동 관리 |
| 스마트 포인터 | 자동 관리, 다형성 유지 | 오버헤드 |
| 복사 금지 | 슬라이싱 방지 | 복사 불가 |
핵심 규칙
- 값 전달 금지 (참조·포인터 사용)
- vector
금지 (vector<unique_ptr > 사용) - 복사 생성자 delete (슬라이싱 방지)
- clone 메서드 (다형성 복사)
체크리스트
- 함수 인자가 참조나 포인터인가?
- 컨테이너가 포인터를 저장하는가?
- 복사 생성자가 delete되어 있는가?
- clone 메서드가 구현되어 있는가?
같이 보면 좋은 글 (내부 링크)
이 주제와 연결되는 다른 글입니다.
- C++ 상속 | Inheritance 완벽 가이드
- C++ 가상 함수 | virtual function 가이드
- C++ 다형성 | Polymorphism 가이드
- C++ 스마트 포인터 | unique_ptr·shared_ptr
마치며
슬라이싱 문제는 다형성을 손실시키는 위험한 버그입니다.
핵심 원칙:
- 값 전달 금지
- 참조·포인터 사용
- 복사 생성자 delete
다형성 객체는 절대 값으로 전달하지 마세요. 참조나 포인터를 사용하세요.
다음 단계: 슬라이싱을 이해했다면, C++ 다형성 가이드에서 더 깊이 배워보세요.
관련 글
- C++ 시리즈 전체 보기
- C++ Adapter Pattern 완벽 가이드 | 인터페이스 변환과 호환성
- C++ ADL |
- C++ Aggregate Initialization |