KoreanFoodie's Study
Effective C++ | 항목 11 : operator= 에서는 자기대입에 대한 처리가 빠지지 않도록 하자 본문
Effective C++ | 항목 11 : operator= 에서는 자기대입에 대한 처리가 빠지지 않도록 하자
GoldGiver 2022. 10. 25. 16:07
C++ 프로그래머의 필독서이자 바이블인, 스콧 마이어스의 Modern Effective C++ 를 읽고 기억할 내용을 요약하고 있습니다. 꼭 읽어보시길 추천드립니다!
항목 11 : operator= 에서는 자기대입에 대한 처리가 빠지지 않도록 하자
핵심 :
1. operator= 를 구현할 때, 어떤 객체가 그 자신에 대입되는 경우를 제대로 처리하도록 만들자. 원본 객체와 복사대상 객체의 주소를 비교해도 되고, 문장의 순서를 적절히 조정할 수도 있으며, 복사 후 맞바꾸기 기법을 써도 된다.
2. 두 개 이상의 객체에 대해 동작하는 함수가 있다면, 이 함수에 넘겨지는 객체들이 사실 같은 객체인 경우에 정확하게 동작하는지 확인하자.
이런 코드는 사실 적법한(legal) 코드이다.
class Widget{ ... }
Widget w;
...
w = w // 자기 자신 대입
사실 자기 자신을 대입할 수 있는 가능성을 가진 코드는 생각보다 종류가 다양하다.
// 가리키는 것이 같다면...
a[i] = a[j];
*px = *py;
// 다른 타입이지만 실제로는 같은 경우
class Base { ... }
class Derived { ... }
// rb 와 *pd 가 같은 객체라면?
void doSomething(const Base& rb, Derived* pd);
위와 같은 경우에도, 자기 대입은 안전하게 동작해야 한다. 예시를 보자.
class Widget
{
...
private:
Bitmap* pb;
};
Widget& Widget::operator=(const Widget& rhs)
{
delete pb;
pb = new Bitmap(*rhs.pb);
return *this;
}
위의 예시에서, 만약 rhs 가 자기 자신이면 어떻게 될까? delete 연산자가 *this 객체의 비트맵에만 적용되는 것이 아니라, rhs 의 객체까지 적용되어 버린다.
간단하게 인자가 자기 자신인지를 체크하는 코드를 삽입해 보자.
Widget& Widget::operator=(const Widget& rhs)
{
if (this == &rhs) return *this;
delete pb;
pb = new Bitmap(*rhs.pb);
return *this;
}
위와 같이 처리하면 잘 될 것 같지만, 사실 이 코드는 예외에 안전하지 않다. 만약 아래에 있는 'new Bitmap' 라인에서 예외가 발생하면, pb 는 삭제된 상태로 남게 되어, 삭제된 Bitmap 을 가리키는 포인터를 계속 가지게 된다.
코드의 순서를 조금 바꾸면, 위에서 언급한 위험성을 어느 정도 해소할 수 있다.
Widget& Widget::operator=(const Widget& rhs)
{
Bitmap* pOrig = pb;
pb = new Bitmap(*rhs.pb);
delete pOrig;
return *this;
}
이 코드는 이제 예외에 안전하다. 'new Bitmap' 부분에서 예외가 발생해도 pb 는 변경되지 않은 상태가 유지되기 때문이다. 또한 일치성 테스트는 분기문을 만들어 성능을 저하시키는데, 그 문제에 있어서도 자유롭다.
또 다른 방법으로는, '복사 후 맞바꾸기(copy and swap)' 이 있다. 예시를 보자.
class Widget
{
// *this 의 데이터 및 rhs의 데이터를 맞바꾼다
void swap(Widget& rhs);
};
Widget& Widget::operator=(const Widget& rhs)
{
Widget temp(rhs);
swap(temp);
return *this;
}
이 방법은 C++ 가 '클래스의 복사 대입 연산자는 인자를 값으로 취하도록 선언하는 것이 가능하다는 점' 과, '값에 의한 전달을 수행하면 전달된 대상의 사본이 생긴다는 점' 을 이용하여 조금 다르게 구현할 수도 있다.
// rhs 는 원래 객체의 '사본' 이다
// 즉, rhs 에 대해 복사 생성자가 불렸다고 보면 된다
Widget& Widget::operator=(Widget rhs)
{
swap(rhs);
return *this;
}
위 코드는 명확성을 제물로 바쳤지만, 객체를 복사하는 코드가 함수 본문으로부터 매개변수의 생성자로 옮겨졌기 때문에, 컴파일러가 더 효율적인 코드를 생성할 수 있는 여지가 만들어 졌다!
'Tutorials > C++ : Advanced' 카테고리의 다른 글
Effective C++ | 항목 13 : 자원 관리에는 객체가 그만! (0) | 2022.10.25 |
---|---|
Effective C++ | 항목 12 : 객체의 모든 부분을 빠짐없이 복사하자 (0) | 2022.10.25 |
Effective C++ | 항목 10 : 대입 연산자는 *this 의 참조자를 반환하게 하자 (0) | 2022.10.25 |
Effective C++ | 항목 9 : 객체 생성 및 소멸 과정 중에는 절대로 가상 함수를 호출하지 말자 (0) | 2022.10.25 |
Effective C++ | 항목 8 : 예외가 소멸자를 떠나지 못하도록 붙들어 놓자 (0) | 2022.10.25 |