본문 바로가기
C++/Effective C++

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

by 멍청이 개발자 2024. 1. 18.
반응형

항목 10은 대입 연산자에서는 반드시 *this를 반환하라는 내용인데 c++ 기본 문법을 배웠다면 너무나도 당연한 얘기여서 건너 뛰었다. 자 이번 항목은 조금 중요한 내용인데 대입연산자를 만들때 자기 대입에 대한 처리를 해야한다는 것이다.

class Widget { ... };
Widget w;
...
w = w; // 자기 대입

솔직히 누가  저런식으로 코드를 멍청하게 짜겠느냐 하겠지만 만약 다음과 같은 상황이라면 충분히 가능하다.

a[i] = a[j];

*px = *py;

만약 i, j가 같다면, px와 py가 가리키는 곳이 동일하다면 자기 대입은 언제든지 발생할 수 있다. 

 

자, 그런데 자기 대입이 왜 문제가 될까? 솔직히 자기 자신을 대입하면 아무런 일이 발생하지 않을거라고 생각할지도 모른다. 그런데 만약 다음과 같은 상황이라면 정말 괜찮을까?

class Bitmap { ... };

class Widget
{
    ...
private:
    Bitmap *pb; // 힙에 할당한 포인터
};

자, 위와 같이 힙 영역에 할당한 포인터를 소유한 객체가 있다고 치자 우리는 여기서 대입 연산자를 설계할때 다음과 같이 할 것이다.

Widget& Widget::operator=(const Widget& rhs)
{
    delete pb;
    pb = new Bitmap(*rhs.pb);
    return *this;
}

자 여기서 자기 대입이 일어나면 자기 자신의 Bitmap포인터를 삭제하고 NULL포인터를 복사하여 Bitmap을 생성한다. 상당히 이상한 행동을 하게되는 것을 알 수 있다. 그렇다면 우리는 어떻게 처리할까? 우선 간단히 논리 연산을 이용하여 해결 할 수 있다.

Widget& Widget::operator=(const Widget& rhs)
{
    if (this == &rhs) return *this;
    
    delete pb;
    pb = new Bitmap(*rhs.pb);
    return *this;
}

하지만 자기 대입이 일어나는 일이 그렇게 흔한 일일까? 아주 드문드문 발생할것인데 항상 이렇게 if문을 거치는것은 처리 속도를 늦추게 된다. 그러면 그냥 if문을 쓰지 않고 항상 같은 방식으로 처리하되 자기 대입이 발생해도 안전하도록만 설계하면 아무 문제가 없지 않을까? 아래코드를 한 번 보자.

Widget& Widget::operator=(const Widget& rhs)
{    
    Bitmap *pOrigin = pb;
    pb = new Bitmap(*rhs.pb);
    delete pOrigin;
    
    return *this;
}

위와 같이 자신의 Bitmap의 포인터를 다른곳에 담아 두고 새로운 객체의 pb를 복사해서 Bitmap을 생성한후 자기 자신의 사본을 삭제하는 방식이다. 이렇게 한다면 아무런 문제 없이 잘 될것이다. 

 

자, 다음으로는 이 문제를 해결할 수 있는 두 번째 방법을 보도록 하자. 두 번째 방법은 copy and swap이라는 복사 후 맞바꾸기라는 기법이다. 아래 코드를 보자. 

class Widget {
    ...
    void swap(Widget& rhs);
    ...
};

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

자, 위와 같이 swap함수를 이용하여 복사본을 생성한후 서로 맞바꾸는 방식이다. 이를 좀더 축약하면 다음과 같이 된다.

Widget& Widget::operator=(Widget rhs)
{
    swap(rhs);    
    return *this;
}

하지만 이 코드는 컴파일러가 코드를 변경할 가능성이 있기 때문이 이전 코드가 더 좋다. 

반응형