본문 바로가기

old drawer/C, C++, MFC

[Effective C++ 요약] 11. operator=에서는 자기대입에 대한 처리가 빠지지 않도록 하자.

class Widget{...};

Widget w;

...

w = w;

 

혹은,


a[i] = a[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;

}

 

이 상황에서 만약 pb와 rhs가 같은 객체를 가리키고 있다면?? 삭제된 객체를 대입 연산하는 결과를 불러온다.

즉, 원치 않은 결과를 얻게 될 것이다는 말이다.

 

이를 해결하기 위한 방법은 여러가지가 있다.

 

Widget& Widget::operator=(const Widget& rhs)

{

if(this == &rhs) return *this;

 

delete pb;

pb = new Bitmap(*rhs.pb);

return *this;

}

 

위와 같이 같은 객체인지를 판단하여 자기 대입에 해당하면 아무것도 하지않고 바로 *this를 리턴하도록 한다.

하지만 이는 여전히 new Bitmap(..)의 부분에서 예외가 발생할 수 있다는 문제점을 않고 있다. 예를 들면 동적 할당을 위한 리소스 부족 같은 ..

 

이를 완전히 해결하는 방법은 다음과 같다.

 

Widget& Widget::operator=(const Widget& rhs)

{

Bitmap *pOrig = pb;

pb = new Bitmap(*rhs.pb);

delete pOrig;

return *this;

}

 

위와 같이 수정하면 new Bitmap(..) 부분에서 예외가 발생하더라도 pb는 변경되지 않은 상태가 유지된다. 이는 원본 Bitmap을 복사해 놓고, 복사해 놓은 사본을 포인터가 가리키게 만든 후, 원본을 삭제하는 순서로 실행되기 때문이다.

 

예외의 안전성과 자기대입 안전성을 동시에 가진 operator=을 구현하는 방법은 '복사 후 맞바꾸기'라 알려진 것으로 다음과 같다.

 

class Widget {

...

void swap(Widget& rhs);

...

};

Widget& Widget::operator=(const Widget& rhs)    //pass by reference

{

Widget temp(rhs);    //rhs에 대한 사본을 만들고

swap(temp);           //*this의 데이터를 그 사본과 바꾼다.

return *this;

};

 

위를 다음과 같이 구현할 수 도 있다.

 

Widget& Widget::operator=(const Widget rhs)    //pass by value

{

swap(temp);

return *this;

};

 

 

- 요약 -

> operator=을 구현할 때, 어떤 객체가 그 자신에 대입되는 경우를 제대로 처리하도록 만들자. 원본 객체와 복사대상 객체의 주소를 비교해도 되고, 문장의 순서를 적절히 조정할 수도 있으며, 복사 후 맞바꾸기 기법을 써도 된다.

> 두 개 이상의 객체에 대해 동작하는 함수가 있다면, 이 함수에 넘겨지는 객체들이 사실 같은 객체인 경우에 정확하게 동작하는지 확인해 보라.