Knight k2 = k1;에서 포인터 주소만 복사되면 어떤 문제가 생기는지 설명할 수 있다.컴파일러가 만들어주는 기본 복사는 멤버를 그대로 복사합니다.
포인터 멤버가 있으면 “객체”가 아니라 “주소”가 복사됩니다.
k1._pet ----┐
├----> Pet 객체(힙)
k2._pet ----┘
결과:
k1과 k2가 같은 Pet 하나를 공유하게 됩니다.delete하면 다른 쪽 포인터는 댕글링이 됩니다.전형적인 사고 흐름:
k1 소멸 -> delete _petk2 소멸 -> 이미 해제된 같은 주소를 다시 delete (이중 delete, UB)또는:
k1이 먼저 delete한 뒤k2._pet->... 접근 시 Use-After-Free 발생(UB)핵심:
깊은 복사는 복사 시점에 새 객체를 따로 만들어 독립 소유하게 합니다.
class Knight {
public:
Knight() = default;
~Knight() { delete _pet; }
// 복사 생성자: 깊은 복사
Knight(const Knight& other)
: _hp(other._hp),
_pet(other._pet ? new Pet(*other._pet) : nullptr)
{
}
// 복사 대입 연산자: 깊은 복사 + 자기대입 방지
Knight& operator=(const Knight& other)
{
if (this == &other)
return *this;
Pet* newPet = other._pet ? new Pet(*other._pet) : nullptr;
delete _pet;
_pet = newPet;
_hp = other._hp;
return *this;
}
private:
int _hp = 0;
Pet* _pet = nullptr;
};
k1._pet ----> Pet 객체 A (힙)
k2._pet ----> Pet 객체 B (힙) // 서로 독립
포인터 같은 “직접 관리 자원”이 멤버로 있으면 보통 3개를 같이 고민해야 합니다.
실전 선택지:
Knight(const Knight&) = delete;Knight& operator=(const Knight&) = delete;std::unique_ptr 등으로 Rule of 0에 가깝게 설계new/delete 자체를 줄여 버그 여지를 크게 줄임