C++/Effective C++
Effective C++ 공부 (항목 11 ~ 15)
둠치킨
2025. 6. 25. 15:50
Effective C++ 요약 (항목 11~15)
항목 11: operator=에서는 자기 대입에 대한 처리를 빠뜨리지 말자
문제점
arr[i] = arr[j];
*px = *py;
- 위 연산들에는 자기 대입(self-assignment)의 가능성이 있음.
- 단순히 delete 후 new로 처리할 경우 자기 대입 시 이미 지워진 데이터를 참조하게 될 수 있음.
안전한 방법
Widget& Widget::operator=(const Widget& rhs) {
if (this == &rhs) return *this;
Bitmap* pOrig = pb;
pb = new Bitmap(*rhs.pb); // 복사 성공 시에만 교체
delete pOrig;
return *this;
}
- 복사 먼저 → delete 순으로 예외 안전성 확보.
- 성능을 고려한다면 이후 항목 29의 “복사 후 교환(copy-and-swap)” 기법도 고려 가능.
항목 12: 객체의 모든 부분을 빠짐없이 복사하자
객체 복사 함수 2개
- 복사 생성자
- 복사 대입 연산자
실수 예시
class Customer {
public:
Customer(const Customer& rhs);
Customer& operator=(const Customer& rhs);
private:
std::string name;
};
Customer::Customer(const Customer& rhs)
: name(rhs.name) { logCall("copy ctor"); }
Customer& Customer::operator=(const Customer& rhs) {
logCall("copy assignment");
name = rhs.name;
return *this;
}
- 나중에 멤버가 추가됐는데 복사 함수 수정 안 하면? → 컴파일러는 알려주지 않음!
- 상속 관계라면 더 위험. 파생 클래스에서 기본 클래스 복사 누락 주의.
해결 방법
- 모든 데이터 멤버 복사
- 기본 클래스의 복사 함수 호출:
PriorityCustomer::PriorityCustomer(const PriorityCustomer& rhs)
: Customer(rhs), ... { ... }
PriorityCustomer& PriorityCustomer::operator=(const PriorityCustomer& rhs) {
Customer::operator=(rhs);
...
}
- 복사 생성자와 복사 대입 연산자는 같은 private 함수 호출로 공통 처리 가능. 직접 호출은 금지.
항목 13: 자원 관리에는 객체가 그만!
예외 발생 시 자원 누수
void f() {
Investment* p = createInvestment();
...
delete p;
}
- 중간에 예외 발생하면 delete가 호출되지 않음 → 누수 발생
해결: RAII(Resource Acquisition Is Initialization)
std::unique_ptr<Investment> p(createInvestment());
- 객체 생성과 동시에 자원 관리
- 블록을 벗어나면 자동으로 delete 호출
- auto_ptr은 복사 시 위험 → C++11부터는 shared_ptr, unique_ptr 사용
항목 14: 자원 관리 클래스의 복사 동작에 대해 진지하게 고찰하자
RAII 클래스의 복사 방식 선택 필요:
- 복사 금지
class Lock {
Lock(const Lock&) = delete;
Lock& operator=(const Lock&) = delete;
};
- 참조 계수 방식 (shared_ptr)
std::shared_ptr<Mutex> mutexPtr;
- 깊은 복사
- 자원을 독립적으로 복제해야 하는 경우
- 소유권 이전 (move)
- auto_ptr처럼 소유권 넘기기 (요즘은 unique_ptr로 대체)
항목 15: 자원 관리 클래스에 관리되는 자원은 외부에서 접근할 수 있게 하자
문제
int daysHeld(const Investment* pi);
std::shared_ptr<Investment> pInv(createInvestment());
int days = daysHeld(pInv); // ❌ 컴파일 에러
- 스마트 포인터는 원시 포인터로 자동 변환되지 않음
해결
int days = daysHeld(pInv.get()); // 명시적 변환
또는
bool taxable = !pInv->isTaxFree(); // 암시적 변환 (operator->, operator*)
- 잘 설계된 RAII 클래스는 get, operator*, operator-> 제공
- 타입 안정성과 명확성을 위해 명시적 변환만 제공하는 것도 고려 가능