Quick Reference
자원을 직접 소유하는 타입은 복사 생성자와 복사 대입이 같은 소유권을 공유하지 않게 해야 합니다. 대입은 새 상태를 먼저 만들고 swap하는 복사-후-교환 방식으로 자기 대입과 할당 실패를 함께 처리할 수 있습니다.
#include <algorithm>
#include <cstddef>
#include <utility>
class Buffer {
public:
explicit Buffer(std::size_t size) : data_{new int[size]}, size_{size} {}
Buffer(const Buffer& other) : Buffer(other.size_) {
std::copy(other.data_, other.data_ + size_, data_);
}
Buffer& operator=(Buffer other) noexcept {
swap(other);
return *this;
}
~Buffer() { delete[] data_; }
void swap(Buffer& other) noexcept {
using std::swap;
swap(data_, other.data_);
swap(size_, other.size_);
}
private:
int* data_ = nullptr;
std::size_t size_ = 0;
};문법
컴파일러가 자동 생성하는 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