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-> 제공
  • 타입 안정성과 명확성을 위해 명시적 변환만 제공하는 것도 고려 가능