자기대입 = 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를 이용하면 처음부터 사본이 날라옴
- 복사 대입 연산자는 call by value가 가능
- call by value는 사본을 전달(값만 전달)
이 코드는 좀 걱정되기도 한다. 일단 객체를 call by value로 전달하는건 그렇게 탐탁지가 않고, 코드의 명확성이 좀 떨어지기 때문이다. 하지만 효율을 더 중시한다면, 객체를 복사하는 코드가 함수 본문에서 parameter 생성자로 옮겨졌기 때문에, 컴파일러가 더 효율적인 코드를 생성할 수 있는 여지가 있다.
- operator=을 구현할 때, 자기대입의 경우를 제대로 처리하도록 하자. 원본 객체와 복사 객체의 주소를 비교해도 되고(일치성 검사, identity test), 문장의 순서를 적절히 조정해도 되고, copy and swap 기법을 써도 된다.
- 두 개 이상의 객체에 대해 동작하는 함수가 있다면, 이 함수에 넘겨지는 객체들이 사실 같은 객체인 경우에 정확하게 동작하는지 확인해 보자.(중복참조, aliasing)
'Effective C++ > 2. 생성자, 소멸자 및 대입 연산자' 카테고리의 다른 글
항목 12: 객체의 모든 부분을 빠짐없이 복사하자 (0) | 2021.04.24 |
---|---|
항목 10: 대입 연자는 *this의 참조자를 반환하게 하자 (0) | 2021.04.24 |
항목 9: 객체 생성 및 소멸 과정 중에는 절대로 가상함수를 호출하지 말자 (0) | 2021.04.24 |
항목 8: 예외가 소멸자를 떠나지 못하도록 붙들어 놓자 (0) | 2021.04.24 |
항목 7: 다형성을 가진 클래스에서는 소멸자를 반드시 가상 소멸자로 선언하자 (0) | 2021.04.24 |