핵심 정리
cpp
class Animal {
public:
virtual ~Animal() = default; // ✅ virtual 소멸자
virtual void speak() const = 0;
};
class Dog : public Animal {
std::string name_;
public:
explicit Dog(std::string name) : name_(std::move(name)) {}
void speak() const override { std::cout << "Woof: " << name_ << "\n"; }
~Dog() { std::cout << "Dog " << name_ << " destroyed\n"; }
};
Animal* a = new Dog("Rex");
delete a; // ✅ Dog 소멸자 → Animal 소멸자 순으로 호출문법
virtual destructor — 기반 포인터로 delete할 때의 위험
Base*로 delete할 때 소멸자가 virtual이 아니면 정적 타입(Base)의 소멸자만 호출됩니다. 파생 클래스 소멸자가 호출되지 않아 파생 클래스의 자원이 누수됩니다.
cpp
class Base {
public:
// ❌ virtual 없음
~Base() { std::cout << "Base dtor\n"; }
};
class Derived : public Base {
int* data_ = new int[100];
public:
~Derived() {
delete[] data_; // ❌ 호출 안 됨 — 메모리 누수
std::cout << "Derived dtor\n";
}
};
Base* p = new Derived();
delete p; // ❌ Base 소멸자만 호출 — Derived::data_ 누수
// ✅ virtual ~Base() = default; 추가 시
// delete p → Derived 소멸자 → Base 소멸자 순으로 올바르게 호출object slicing — 값 복사 시 파생 부분 소실
파생 객체를 기반 클래스 값으로 복사하면 기반 클래스 크기만큼만 복사되고 파생 부분이 잘려 나갑니다. 이후 virtual dispatch가 의도대로 동작하지 않습니다.
cpp
struct Animal {
virtual std::string sound() const { return "..."; }
};
struct Dog : Animal {
std::string sound() const override { return "Woof"; }
};
Dog dog;
Animal a = dog; // ❌ object slicing — Dog 부분 제거됨
a.sound(); // "..." — Dog::sound 아님, Base::sound 호출
// ❌ 벡터에 값으로 저장 — 삽입 시 슬라이싱
std::vector<Animal> animals;
animals.push_back(Dog{}); // ❌ Dog 부분 잘림
// ✅ 포인터/스마트 포인터로 다형성 유지
std::vector<std::unique_ptr<Animal>> animals;
animals.push_back(std::make_unique<Dog>());
animals[0]->sound(); // ✅ "Woof"다형성의 핵심 — 참조/포인터로만 유지
다형성은 값 복사 시 사라집니다. 기반 클래스 참조나 포인터를 통해서만 virtual dispatch가 올바르게 동작합니다.
cpp
void makeSound(Animal& a) { // ✅ 참조 — 슬라이싱 없음
a.sound(); // 동적 타입에 따라 올바른 함수 호출
}
void makeSound(Animal a) { // ❌ 값 — 슬라이싱 발생
a.sound(); // 항상 Animal::sound 호출
}
Dog d;
makeSound(d); // 참조 버전: "Woof" ✅ / 값 버전: "..." ❌체크포인트
| 상황 | 적합한 선택 |
|---|---|
| 기반 포인터로 delete 가능성 있을 때 | virtual ~Base() = default |
| 다형성을 컨테이너에 담을 때 | vector<unique_ptr<Base>> |
| 함수에서 다형성 유지 | Base& 또는 Base* 인자 |
| 복사는 허용하지 않는 추상 기반 클래스 | = delete copy ctor/assignment |
주의할 점
다형적으로 사용할 기반 클래스에 virtual 소멸자가 없으면 파생 소멸자가 호출되지 않아 자원이 누수됩니다.
cpp
// ❌ virtual 소멸자 없음 + 기반 포인터로 delete
struct Base { ~Base() {} };
struct Derived : Base {
std::string data{"lots of data"};
~Derived() { std::cout << "cleaned\n"; }
};
Base* p = new Derived();
delete p; // ❌ "cleaned" 출력 안 됨 — data 누수, UB
// ✅ virtual 소멸자 추가
struct Base { virtual ~Base() = default; };
delete p; // ✅ Derived 소멸자 → Base 소멸자
// ❌ 함수 인자를 값으로 받으면 slicing 발생
void process(Base b) { b.virtualFn(); } // ❌ 항상 Base 버전 호출
// ✅ 참조로 받아야 다형성 유지
void process(Base& b) { b.virtualFn(); } // ✅ 동적 타입 기준 호출참고 링크
2 sources