자기 대입(self-assignment)이란?
어떤 객체가 자기 자신에 대해 대입 연산을 하는 것.
class Widget{...};
Widget w;
w = w;
// a[i] = a[j] <- i == j인 경우
// *px = *py <- px, py가 가리키는 데이터가 동일.
자기 대입을 하게 되면 생기는 문제?
같은 객체를 여러 개가 참조(중복 참조 : aliasing) 하기 때문에 한쪽에서 객체를 소멸시키면 나머지 참조자 또는 포인터들은 null 객체를 가지게 된다.
따라서, 자기 대입 연산자 (self-assignment operator) 를 이용할 때 효율적으로 자원관리를 하기 위해서 주의해야 한다.
Widget& Widget::operator=(const Widget& rhs){
delete pb;
pb = new Bitmap(*rhs.pb);
return *this;
}
rhs로 자기 자신이 들어오게 된 경우 pb는 자원이 삭제된 상태를 가지게 된다.
또한, 자기 대입 부분이 아니더라도 return 이전에 예외가 발생하는 경우 삭제된 pb를 가진 객체로 존재할 수 있다.
따라서 자기대입(self-assignment)와 예외에 대해 safe 한 코드로 바꿔줘야 한다.
1. 자기 대입에 대해 safe : Identity test
Widget& Widget::operator=(const Widget& rhs){
if(this == &rhs) return *this;
delete pb;
pb = new Bitmap(*rhs.pb);
return *this;
}
자기 자신인지를 체크하는 코드를 삽입함으로써 자기 대입 연산을 방지할 수 있다.
2. 예외에 대해 safe.
Widget& Widget::operator=(const Widget& rhs){
Bitmap* pOrig = pb;
pb = new Bitmap(*rhs.pb);
delete pOrig;
return *this;
}
Bitmap *pOrig 에 원래 가지고 있었던 pb 포인터를 대입해놓고, pb에는 새로운 Bitmap을 생성해 가리키게 한다.
이렇게 하면 new Bitmap() 과정에서 예외가 발생하더라도 원래 가지고 있던 pb에 영향을 주지 않게 되어 예외에 safe 하다.
또한, 자기 대입 연산을 하는 경우에도 *pb의 사본을 가리키게 만들기 때문에 자기 대입에서도 safe를 유지할 수 있다.
3. Copy and Swap 방식
Widget& Widget::operator=(const Widget& rhs){
Widget temp(rhs);
swap(temp);
return *this;
}
사본을 만들어서 사본과 *this를 바꾸는 방식이다. 2와 달리 new를 하는 대신에 사본 자체를 만들어서 대체하는 방식이다.
위 코드는 파라미터를 값에 의한 전달을 이용해서 동일하게 아래와 같이 구현할 수 있다.
Widget& Widget::operator=(Widget rhs){
swap(temp);
return *this;
}
파라미터를 값으로 전달하게 되면 인자가 넘어갈 때 객체의 사본(copy)으로 전달되기 때문에 복사생성자를 통한 복사를 해주지 않아도 된다.
'Effective C++' 카테고리의 다른 글
이펙티브(Effective) C++ Item13 : 자원 관리에는 객체가 그만! auto_ptr, shared_ptr (0) | 2022.05.22 |
---|---|
이펙티브(Effective) C++ Item 12 : 객체의 모든 부분을 빠짐없이 복사하자 (0) | 2022.05.22 |
이펙티브(Effective) C++ Item8 : 예외가 소멸자에서 떠나지 못하도록 붙들어 놓자. (0) | 2022.05.15 |
이펙티브(Effective) C++ Item 9 : 객체 생성 및 소멸 과정 중에는 절대로 가상 함수를 호출하지 말자. (0) | 2022.05.15 |
이펙티브(Effective) C++ Item 10 : 대입 연산자는 *this의 참조자를 반환하게 하자. (0) | 2022.05.14 |
댓글