Item 11: operator=에서는 자기대입에 대한 처리가 빠짖 않도록 하라

1. 자기대입(self assignment)이란

어떤 객체가 자기 자신에 대해 대입연산자를 적용하는 것을 말한다.

class Widget { ... };
Widget w;
...
w = w;                  // 자기에 대한 대입
  1. 자기 대입에 빠질 수 있는 예를 2가지 들어라.
a[i] = a[j]; // i == j 이면 자기 대입이 된다.

*px = *py;   // px와 py가 같은 객체를 가리키면
             // 자기대입이 된다.
3. 아래 코드의 문제점은?
class Bitmap { ... };

class Widget {
    Widget& operator=(const Widget& rhs);
    ...
private:
    Bitmap *pb;       // 힙에 할당한 객체를 가리키는 포인터
};

Widget& Widget::operator=(const Widget& rhs) // 안전하지 않은 구현
{
    delete pb;                               // 현재 비트맵 사용 중지
    pb = new Bitmap(*rhs.pb);                // 이제 rhs의 비트맵을 사용하게 한다.
    return *this;
}

첫번째, *this와 rhs가 같은 객체인 경우 delete pb 부분이 rhs와 *this에 대해서 모두 적용되어 pb는 이상한 값이 된다.

둘째, 만약 new Bitmap(*rhs.pb)에서 예외가 발생하게 된다면 delete pb는 회복되지 않는다.

4. 해결책은?
  • 자기대입에 대한 문제 해결
Widget& operator=(const Widget& rhs)
{
    if (this == &rhs) return *this;

    delete pb;
    pb = new Bitmap(*rhs.pb);
    return *this;
}
  • new에서 예외 발생에 대한 해결
Widget& operator=(const Widget& rhs)
{
    Bitmap *pOrig = pb;
    pb = new Bitmap(*rhs.pb);
    delete pOrig;
    return *this;
}

이 코드는 예외와 자기대입에 대해서 안전하다.

  • 예외가 발생해도 pb는 변경되지 않은 상태를 유지한다.
  • 원본 비트맵을 복사해 놓고, 복사해 놓은 사본을 포인터가 가리키게 한 후, 원본을 삭제하는 순서로 하기 때문에 자기대입 문제도 해결이된다.
5. 복사 후 맞바꾸기(copy and swap) 방법이란 무엇인가?
class Widget {
    ...
    void swap(Widget& rhs);  // 자세한 내용은 아이템 29 참조
    ...
};

Widget& Widget::operator=(const Widget& rhs)
{
    Widget temp(rhs);
    swap(temp);             // *this의 데이터를 그 사본의 것과 맞바꾼다.
    return *this;
}

또 다른 방법

Widget& Widget::operator=(Widget rhs) // 참조자가 아니다.
{
    swap(rhs);                        // *this의 데이터를 그 사본의 것과 맞바꾼다.

    return *this;
}

정리

  • operator=을 구현할 때, 어떤 객체가 그 자신에 대입된느 경우를 제대로 처리하자. 원본객체와 복사대상 객체의 주소를 비교해도 되고, 문장의 순서를 적절히 조정할 수도 있으며, 복사후 맞바꾸기 기법을 써도된다.
  • 두 개 이상의 객체에 대해 동작하는 함수가 있다면, 이 함수에 넘겨지는 객체들이 사실 같은 객체인 경우에 정확하게 동작하는지 확인하자.

results matching ""

    No results matching ""