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

C++/Effective C++

2020. 7. 28. 21:48

1. 자기 대입이란?

class Widget{...};

Widget w;

w =w;

 

이런 경우는 빈번하게 일어난다.

 

a[i] = a[j] ;

*px = *py;

 

위와 같은 경우는 실수하기가 쉽다! 보통, 이러한 자기 대입이 생기는 이유는 여러 곳에서 하나의 객체를 참조하는 상태, 다시 말해 중복 참조라고 불리는 것 때문이다. 같은 타입으로 만들어진 객체 여러 개를 참조자 혹은 포인터로 물어 놓고 동작하는 코드를 작성할 때는 같은 객체가 사용 될 가능성을 고려하는 것이 일반적으로 바람직한 자세이다.

 

2. 대입 연산자와 자기 대입

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

*this와 rhs가 같은 객체일 가능성이 있다. 이 둘이 같은 객체이면, delete 연산자가 *this 객체의 비트맵에만 적용되는 것이 아니라 rhs의 객체까지 적용되어 버린다.

 

3. 해결법 3가지

  • 일치성 검사
Widget& Widget::operator = (const Widget& rhs)
{
	if(this == &rhs) return *this;
	delete pb;
    	pb = new Bitmap(*rhs.pb);
    
   	return *pb;
}

단점으로 분기에 의한 비용 문제가 있다. 개인적으로 가장 간단하기 때문에 적극 추천한다. 홍정모 교수님(지금은 아니시지만..)의 c++ 강의에서도 이처럼 일치성 검사를 통해 중복 참조를 피하였다.

그리고, 만약 Bitmap 생성중 예외 사항이 발생한 경우 원본인 pb의 메모리도 해제되는 문제가 있다.

 

  • 코드 순서 바꾸기
Widget& Widget::operator = (const Widget& rhs)
{
    Bitmap* pOrig = pb;
    pb = new Bitmap(*rhs.pb);
    delete pOrig;
    
    return *this;
}

자기 대입이 일어나는 경우를 적다 가정할 때 가장 좋은 방법이다. 그리고 원본 객체를 복사해두고 예외검사를 하기에 실패 했을 경우에 대한 안정성이 보장된다.

 

  • 복사 후 맞바꾸기
class Widget {

    void swap(Widget& rhs);
};

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

이 방법은 간단히 말하면 복사 대상의 사본을 만들어 놓은 후 모든 작업을 완료 하고 예외가 발생하지 않으면 swap 함수를 통해 사본과 원본을 교체하는 방식이다. 예외가 던져져도 원본 자체는 손상되지 않기때문에 예외안정성이 보장된다.

 

이것만은 잊지 말자!

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