방프리

17.07.10. Effective C++ 2. 생성자, 소멸자 및 대입 연산자 (항목11) 본문

C++/Effective C++

17.07.10. Effective C++ 2. 생성자, 소멸자 및 대입 연산자 (항목11)

방프리 2020. 1. 7. 05:21

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

 

이 항목을 살펴보기 전 자기대입이라는 것이 대해 알아보겠습니다. 자기대입이란?

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

int나 일반 데이터 타입의 경우 자체적으로 대입 행동의 정의가 되어 있지만 사용자 정의 class나 구조체의 

경우 어떤 행동을 해야할 지 모르기 때문에 정의를 해주어야 합니다.

일반적으로 개발자들이 생각하는 대입 코드를 알아보겠습니다. 

Widget w, w1;

w =w1;

보통의 데이터 타입일 경우 통할 수도 있겠지만 사용자 정의 클래스에서는 어림없는 문법이죠.

무슨 행동을 해야하는 지도 모르고 어떤 데이터를 대입해야할지 아무것도 모릅니다. (컴파일러는 천재가 아닙니다.)

다른 경우를 한번 볼까요??

*px = *py;

오 포인터를 사용했습니다. 같은 주소를 대입시켜주는군요. 언뜻보면 맞는 것 같지만 심각한 오류를 가지고 있습니다.

바로 중복참조(aliasing)입니다.

같은 주소를 공유하고 있기 때문에 포인터가 삭제될 경우 어떤 오류를 가지고 올 지 아무도 모르죠

그럼 대입을 할 때 객체를 생성해버리는 것은 어떨까요??

 

Widget::operator=(const Widget& arg)

{

Bitmap *pOrig = pb;

pb = new Widget(*rhs.pb);

delete pOrig;

 

return *this;

}

 

이렇게 구성하면 객체가 같은 메모리를 가리키는 현상도 일어나지 않을 뿐더러 확실하게 구분이 되어버립니다.

하지만 이 코드도 문제점이 있습니다. 바로 일치성 검사 코드인데요.

이 일치성 검사 코드는 굉장히 많은 비용을 소모합니다.

 

그 뿐만 아니라 처리 흐름에 분기를 만들게 되므로 실행 속도도 떨어질 뿐더러 CPU명령어 선행인출, 캐시,

파이프라이닝 등의 효과도 떨어지게 됩니다.

다음의 예제 코드를 보겠습니다.

 

class Widget

{

void swap(Widget& rhs);

};

 

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

{

Widget temp(rhs);

 

swap(temp);

 

return *this;

}

 

어떤가요? 아직 이해가 안되신 분들을 위해 찬찬히 살펴보겠습니다.

일단 매개변수로 넘어온 rhs는 값에 의한 전달입니다.

포인터로 전달한것이 아니기 때문에 call-by-value 형식으로 온 것이죠. 즉, 값으로 전달받은 사본을 반환하는 것입니다.

따로 생성하는 연산을 거치지 않고 전달받은 사본을 이용하는 것이죠. 

 

이것만은 잊지 말자!

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

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

Comments