항목 11 : operator=에서는 자기대입에 대한 처리가 빠지지 않도록 하자
동적 할당된 비트맵을 가리키는 원시 포인터를 데이터 멤버로 갖는 클래스를 하나 만들었다고 가정해 봅시다.
1
2
3
4
5
6
7
|
class Bitmap { ... };
class Widget {
...
private :
Bitmap *pb; // 힙에 할당한 객체를 가리키는 포인터
};
Colored by Color Scripter
|
이젠 겉보기에 멀쩡해 보이는 operator=의 구현코드를 보시겠습니다.
1
2
3
4
5
6
7
8
|
Widget&
Widget::operator=(const Widget& rhs) // 안전하지 않게 구현된 operator=
{
delete pb; // 현재의 비트맵 사용을 중지합니다.
pb = new Bitmap(*rhs.pb); // 이제 rhs의 비트맵을 사용하도록 만듭니다.
return *this;
}
Colored by Color Scripter
|
cs |
여기서 찾을 수 있는 자기 참조 문제는 operator= 내부에서 *this와 rhs가 같은 객체일 가능성이 있다는 것입니다.
이런 에러에 대한 대책으로 operator= 첫머리에서 일치성 검사를 통해 자기대입을 점검하는 것 입니다.
1
2
3
4
5
6
7
8
9
|
Widget& Widget::operator=(const Widget& rhs)
{
if(this == &rhs) return *this; // 객체가 같은지, 즉 자기대입인지 검사합니다. 자기대입이면 아무것도 안합니다.
delete pb;
pb = new Bitmap(*rhs.pb);
return *this;
}
Colored by Color Scripter
|
하지만 이렇게 했을 때 아직 예외 안정성에 대해서는 문젯거리를 가지고 있습니다.
new Bitmap 부분에서 예외가 생기면 Widget객체는 삭제된 Bitmap을 가리키는 포인터를 껴안고 홀로 남고 맙니다.
전부는 아니지만, 다행스럽게도 operator=을 예외에 안전하게 구현하면 대개 자기대입에도 안전한 코드가 나오게 되어 있습니다. 그러니까, 예외 안전성에만 집중하면 자기대입 문제는 무시하더라도 무사히 넘어갈 확률이 높아진다는 것입니다.
"많은 경우에 문장 순서를 세심하게 바꾸는 것마으로 예외에 안전한(동시에 자기대입에 안전한) 코드가 만들어진다"라는 법칙 한 가지를 여기서 써먹어 보도록 하겠습니다.
1
2
3
4
5
6
7
8
|
Widget& Widget::operator=(const Widget& rhs)
{
Bitmap *pOrig = pb; // 원래의 pb를 어딘가에 기억해 둡니다.
pb = new Bitmap(*rhs.pb); // 다음, pb가 *pb의 사본을 가리키게 만듭니다.
delete pOrig; // 원래의 pb를 삭제합니다.
return *this;
}
Colored by Color Scripter
|
예외 안전성과 자기대입 안전성을 동시에 가진 operator=을 구현하는 방법으로, 방금 본 예처럼 문장의 실행 순서를 수작업으로 조정하는 것 외에 다른 방법을 하나 더 알려드리겠습니다. '복사 후 맞바꾸기'라고 알려진 기법입니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
class Widget {
...
void swap(Widget& rhs); // *this의 데이터 및 rhs의 데이터를 맞바꿉니다.
...
};
Widget& Widget::operator=(const Widget& rhs)
{
Widget temp(phs); // rhs의 데이터에 대해 사본을 하나 만듭니다.
swap(temp); // *this의 데이터를 그 사본의 것과 맞바꿉니다.
return *this;
}
Colored by Color Scripter
|
이 방법은 C++가 가진 두 가지 특징을 활용해서 조금 다르게 구현할 수도 있습니다.
1. 클래스의 복사 대입 연산자는 인자를 값으로 취하도록 선언하는 것이 가능하다.
2. 값에 의한 전달을 수행하면 전달된 대상의 사본이 생긴다.
1
2
3
4
5
6
7
|
Widget& Widget::operator=(Widget rhs) // rhs는 넘어온 원래 객체의 사본입니다.
{
swap(rhs); // *this의 데이터를 그 사본의 것과 맞바꿉니다.
return *this;
}
Colored by Color Scripter
|
꼭 잊지 말아야 할 것!
1. operator=을 구현할 때, 어떤 객체가 그 자신에 대입되는 경우를 제대로 처리하도록 만듭시다. 원본 객체와 복사대상 객체의 주소를 비교해도 되고, 문장의 순서를 적절히 조정할 수도 있으며, 복사 후 맞바꾸기 기법을 써도 됩니다.
2. 두 개 이상의 객체에 대해 동작하는 함수가 있다면, 이 함수에 넘겨지는 객체들이 사실 같은 객체인 경우에 정확하게 동작하는지 확인해 보세요.
'언어 > C++' 카테고리의 다른 글
[Effective C++] 자원 관리에는 객체가 그만! (0) | 2020.01.13 |
---|---|
[Effective C++] 객체의 모든 부분을 빠짐없이 복사하자 (0) | 2020.01.12 |
[Effective C++] 대입 연산자는 *this의 참조자를 반환하게 하자 (0) | 2020.01.10 |
[Effective C++] 객체 생성 및 소멸 과정 중에는 절대로 가상 함수를 호출하지 말자 (0) | 2020.01.09 |
[Effective C++] 예외가 소멸자를 떠나지 못하도록 붙들어 놓자 (0) | 2020.01.08 |