[C++] 항목 11: operator=에서는 자기대입에 대한 처리가 빠지지 않도록 하자

업데이트:     Updated:

카테고리:

태그:

이 글은 아래의 책을 자세히 정리한 후, 정리한 글을 GPT에게 요약을 요청하여 작성되었습니다.
이펙티브 C++ 제3판, 스콧 마이어스 저자, 곽용재 번역

📦 2. 생성자, 소멸자 및 대입 연산자

👉🏻 항목 11: operator=에서는 자기대입에 대한 처리가 빠지지 않도록 하자

  • w = w, a[i] = a[j], *px = *py 같은 코드에서 동일한 객체에 대한 대입이 일어날 수 있음.
  • 겉보기엔 문제 없어 보여도, 자원(resource)을 관리하는 객체라면 자기대입 시 심각한 오류 발생 가능.

⚠️ 문제되는 코드 예시

Widget& Widget::operator=(const Widget& rhs) {
	delete pb;
	pb = new Bitmap(*rhs.pb); // 예외 발생 가능
	return *this;
}
  • *this == rhs인 경우 → pb 삭제 후 복사 → 예외 발생 시 dangling pointer (삭제된 메모리 가리킴)
  • 자기대입도 위험, 예외 발생도 위험

✅ 해결 방법 1: 문장 순서 조정

Widget& Widget::operator=(const Widget& rhs) {
	Bitmap* pOrig = pb;
	pb = new Bitmap(*rhs.pb);
	delete pOrig;
	return *this;
}
  • 먼저 복사, 그 후 삭제 → 예외가 발생해도 기존 상태 유지됨
  • 자기대입이든 아니든, 예외가 나든 안 나든 안전하게 동작

❓ 자기대입 체크는 필요 없을까?

if (this == &rhs) return *this;
  • 드물게 발생하는 자기대입만 막기 위해 불필요한 분기, 코드 복잡도, 성능 저하 발생
  • 문장 순서 조정으로 안전하게 만들면, 체크 안 해도 OK

🔁 해결 방법 2: 복사 후 맞바꾸기 (Copy-and-Swap)

개념 설명

  • 먼저 매개변수 복사본을 만들고, 그 복사본과 현재 객체의 내부 자원을 맞바꾼다(swap)

  • swap 함수는 두 객체의 자원을 빠르게 맞바꾸는 함수 (예: 포인터 변수 2개를 단순히 바꾸는 것)

void swap(Widget& a, Widget& b) {
	std::swap(a.pb, b.pb); // std::swap을 써서 내부 포인터만 바꿈
}

방법 1: 명시적 복사 + swap

Widget& Widget::operator=(const Widget& rhs) {
	Widget temp(rhs); // 복사 생성자로 사본 생성
	swap(*this, temp); // *this와 temp의 내부 데이터를 맞바꿈
	return *this;
}

방법 2: 값 전달로 암시적 복사 + swap

Widget& Widget::operator=(Widget rhs) { // rhs는 이미 복사본
	swap(*this, rhs); // *this와 rhs를 맞바꿈
	return *this;
}
  • 이 방식은 복사 생성자만 잘 구현돼 있으면 자기대입 + 예외 안전성을 자동으로 확보할 수 있음

🧐 정리

  • operator=에서는 자기대입과 예외 모두 꼭 고려해야 한다.

  • 해결 방법 3가지:

    1. 일치성 테스트: if (this == &rhs) → 단순하지만 덜 우아하고 성능 손해 있음
    2. 문장 순서 조정: → 먼저 복사, 그다음 삭제 → 안전하고 효율적
    3. 복사 후 맞바꾸기 (Copy-and-Swap): → 복사본과 *this의 자원을 swap 함수로 교환 → 복사 생성자와 swap만 잘 있으면 매우 깔끔하고 안전한 방식
  • 특히 자원(Resource)을 다루는 클래스는 자기대입과 예외 안전성을 항상 고려한 operator=가 필요함.

  • 또한 두 객체가 실은 같은 객체일 수도 있다는 가능성을 잊지 말자.

Cpp 카테고리 내 다른 글 보러가기

댓글남기기