본문 바로가기

Effective C++/2. 생성자, 소멸자 및 대입 연산자

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

자기대입 = self assignment

a=a;

a[i] = a[j];         // i==j일시 자기대입

*pa = *pb;        // pa pb 같은 객체를 가르킬 자기대입

void doSomething(const Base& pb, Derived* pd)   // pb pd 같은 객체를 가르킬 가능성이 있음

모두 자기 대입의 가능성이 있거나, 자기 대입임

중복참조(aliasing) == 여러곳에서 하나의 객체를 가르킴 => 자기대입 가능성

참고)

 

this == &t 비교하냐? 만약 t = t; 이렇게 되있으면(Self Copy) 저게 없으면 t 지워버리고 다시 할당받을것을 가르키는데 문제는 복사할 구조체도 같이 없어지기 때문에 복사가 안됨 >> Self Copy이면 바로 Return 해버림

 

 

Assignment Operator Deep Copy 해야한다면, 반드시 메모리를 해제하고 다시 할당하고 복사해야한다.

그런지는 메모리 구조를 그려봐라.

 

코드는 자기대입(자기참조) 가능성이 있고, 예외에도 안전하지 않음

 

 

identity test 통해 자기대입에는 안전해졌지만, 예외에선 안전하지 않다.

특히 pb = new Bitmap(*rhs.pb); 여기서 예외가 터지면(동적 할당에 필요한 메모리가 부족하다던지, Bitmap 클래스의 복사 생성자에서 예외를 던지던지) Widget 객체는 삭제된 Bitmap 가르키는 dangling pointer 가능성이 있다.

 

 

rhs 값을 *this 복사하는데,

일단 this->pb(원본) 백업해두고, Bitmap(rhs.pb) => Bitmap(Bitmap& obj) 형태이니까 copy cnstr rhs.pb 새로만든 Bitmap 복사함 (copy cnstr 없을 default copy cnstr 복사해줌, 이때 shallow copy 일어나니 조심), 그리고 pb 새로만든 Bitmap 가르키게함(새로만든 Bitmap 내용물이 정확히 *rhs.pb 똑같다, bitwise constness)

그리고 원본 pb 삭제한다.

원본 비트맵을 복사해 놓고, 복사해 놓은 사본을 포인터가 가르키게 , 원본 비트맵을 삭제 => 자기대입현상 완벽히 처리

여기에 일치성 검사까지 붙이는건 오바임. why? 어쨌든 코드가 길어지고, 처리 흐름에 분기를 만들게 되니 실행 시간 속력이 줄어듬, CPU 명령어 선행인출(instruction prefetch), 캐시, 파이프라이닝 등의 효과도 떨어지고 애초에 자기대입은 특수한 상황이라 그리 많지 않을것이다.

 

예외 처리 + 자기대입 안정성을 동시에 잡는 하나의 방법

copy and swap(복사 맞바꾸기)

 

 

w1 = w2; 라고 하면 w2 사본을 하나 만들고, w1 사본의 내용을 맞바꾼 w1 리턴하는 것이다.

swap 함수의 자세한 구현방식은 항목 29 있다.

C++ 특징을 이용하여 간단히 구현가능.

 

call by value 이용하면 처음부터 사본이 날라옴

  1. 복사 대입 연산자는 call by value 가능
  2. call by value 사본을 전달(값만 전달)

코드는 걱정되기도 한다. 일단 객체를 call by value 전달하는건 그렇게 탐탁지가 않고, 코드의 명확성이 떨어지기 때문이다. 하지만 효율을 중시한다면, 객체를 복사하는 코드가 함수 본문에서 parameter 생성자로 옮겨졌기 때문에, 컴파일러가 효율적인 코드를 생성할 있는 여지가 있다.


  • operator= 구현할 , 자기대입의 경우를 제대로 처리하도록 하자. 원본 객체와 복사 객체의 주소를 비교해도 되고(일치성 검사, identity test), 문장의 순서를 적절히 조정해도 되고, copy and swap 기법을 써도 된다.
  • 이상의 객체에 대해 동작하는 함수가 있다면, 함수에 넘겨지는 객체들이 사실 같은 객체인 경우에 정확하게 동작하는지 확인해 보자.(중복참조, aliasing)