핵심 정리
class Buffer {
public:
explicit Buffer(int size)
: data_{new int[size]}, size_{size} { }
// 복사 생성자 — 깊은 복사
Buffer(const Buffer& other)
: data_{new int[other.size_]}, size_{other.size_} {
std::copy(other.data_, other.data_ + size_, data_);
}
// 복사 대입 연산자
Buffer& operator=(const Buffer& other) {
if (this == &other) return *this; // 자기 대입 방어
delete[] data_;
data_ = new int[other.size_];
size_ = other.size_;
std::copy(other.data_, other.data_ + size_, data_);
return *this;
}
~Buffer() { delete[] data_; }
private:
int* data_;
int size_;
};문법
컴파일러가 자동 생성하는 6개 특수 멤버 함수
소멸자, 복사 생성자, 복사 대입, 이동 생성자, 이동 대입, 기본 생성자. 이것들을 직접 정의하지 않으면 컴파일러가 멤버별 얕은 복사/이동을 수행하는 버전을 자동 생성합니다. 동적 자원을 관리하는 클래스에서는 이 기본 버전이 문제를 일으킵니다.
class Naive {
int* data_;
public:
Naive(int n) : data_{new int[n]} { }
~Naive() { delete[] data_; }
// 복사 생성자/대입을 정의 안 하면 컴파일러가 포인터 복사(얕은 복사)를 생성
};
Naive a{10};
Naive b = a; // data_가 같은 포인터를 가리킴
// a, b 소멸 시 같은 포인터 두 번 delete → double free (정의되지 않은 동작)3의 규칙 (Rule of Three)
소멸자를 직접 정의했다면 복사 생성자와 복사 대입 연산자도 함께 정의해야 합니다. 소멸자가 필요하다는 것은 자원을 직접 관리한다는 뜻이고, 기본 얕은 복사가 잘못 동작한다는 의미입니다.
C++11 이후에는 이동 연산까지 포함해 5의 규칙(Rule of Five) 이라고 합니다.
class Managed {
public:
Managed(int size);
~Managed(); // 1. 소멸자
Managed(const Managed& o); // 2. 복사 생성자
Managed& operator=(const Managed& o); // 3. 복사 대입
Managed(Managed&& o) noexcept; // 4. 이동 생성자 (Rule of Five)
Managed& operator=(Managed&& o) noexcept; // 5. 이동 대입
};자기 대입 방어
복사 대입 연산자에서 a = a 같은 자기 대입이 발생하면 기존 자원을 해제한 후 복사하려는 소스가 이미 해제된 상태가 됩니다.
Buffer& operator=(const Buffer& other) {
if (this == &other) return *this; // ✅ 자기 대입이면 아무것도 하지 않음
delete[] data_; // 기존 자원 해제
data_ = new int[other.size_]; // 새 자원 할당
size_ = other.size_;
std::copy(other.data_, other.data_ + size_, data_);
return *this;
}체크포인트
| 상황 | 적합한 선택 |
|---|---|
| 동적 자원 관리 | 5의 규칙 — 5개 특수 멤버 전부 정의 |
| 자원 관리 없는 단순 클래스 | = default — 컴파일러 생성 버전 |
| 복사 금지 (유일한 소유권) | = delete 복사 생성자·대입 |
| 깊은 복사 | 복사 생성자에서 자원 새로 할당 |
| 자원 중복 해제 방지 | 스마트 포인터 사용 (Rule of Zero) |
주의할 점
동적 자원을 관리하는 클래스에서 복사 생성자와 대입을 정의하지 않으면 얕은 복사로 인한 이중 해제(double free)가 발생합니다.
class Leak {
int* data_;
public:
Leak(int n) : data_{new int[n]} { }
~Leak() { delete[] data_; }
// ❌ 복사 생성자/대입 미정의 — 컴파일러의 얕은 복사 사용
};
Leak a{10};
{
Leak b = a; // b.data_ == a.data_ (같은 포인터)
} // b 소멸 — data_ 해제
// a 소멸 — 이미 해제된 data_ 다시 해제 → double free (UB)
// ✅ 해결책 1: 깊은 복사 구현
// ✅ 해결책 2: 복사 금지 + 이동만 허용
// ✅ 해결책 3: std::unique_ptr 사용 (Rule of Zero)참고 링크
1 sources