[Effective C++] #11 중복 참조와 자기 대입 연산자
Scott Meyers의 "Effective C++"를 통해, C++ 구현에 필요한 개념들을 이해하고, 기록하기 위함입니다. 해당 항목은 2장 "생성자, 소멸자 및 대입 연산자", 항목 11 "operator=에서는 자기 대입에 대한 처리가 빠지지 않도록 하자"에 해당하는 내용입니다.
중복 참조에 의한 자기 대입 연산
class Wclass {...};
Wclass w;
...
w = w;
*px = *py; // 서로 같은 객체를 가리킬 수도 ...
a[i] = a[j]; // 같은 값일 수도...
보통 자기 대입 연산은 적법한 코드로 분류됩니다. 쉽게 말해, 사용자 쪽에서 문제가 발생한지 모를 수 있죠. 위 예제 코드는 일단 보기에 크게 문제 될 건 없어 보입니다. 하지만 밑에 있는 예제 코드를 살펴보죠
A& A::operator=(const A& 우변_객체){
delete pA; // 클래스 A를 가리키는 포인터의 비트 할당을 해제합니다.
pA = new A(*우변_객체.pA); // 우변 객체의 포인터를 좌변 객체의 포인터가 가리키도록 합니다.
return *this;
}
대입 연산을 적용한 두 객체가 만약 같은 객체라면, 위 코드는 큰 문제가 될 수 있습니다. "delete"연산은 A를 가리키는 포인터 객체의 비트 할당을 해제합니다. 이때, 좌변의 객체와 우변의 객체가 같다면, 두 객체 모두 자원이 해제됨과 동시에 텅 비어있는 객체 간의 무의미한 대입 연산이 발생하겠죠. 따라서, 우리는 중복 참조자에 대한 자기 대입 연산을 방지하기 위해 여러 가지 방법들이 필요하겠죠. 아래에서 살펴보겠습니다.
1. 일치성 검사
A& A::operator=(const A& 우변_객체){
if(this == &우변_객체) return *this; // 일치성 검사
delete pA; // 클래스 A를 가리키는 포인터의 비트 할당을 해제합니다.
pA = new A(*우변_객체.pA); // 우변 객체의 포인터를 좌변 객체의 포인터가 가리키도록 합니다.
return *this;
}
간단한 방법부터 보겠습니다. 대입 연산 내부의 시작을 일치성 검사로 시작하는 방법입니다. 우변의 객체와 좌변의 객체가 같다면, 좌변의 객체를 반환하고 끝냅니다. 하지만, 또 다른 위협이 도사리고 있죠. 바로 "new A"를 통해 동적할당하는 부분에서 예외가 발생하면 pA는 텅 빈 포인터를 갖고 홀로 남게 되겠죠. 여기서 우리는 예외에도 안전한 소스코드로 수정해 보려 합니다.
A& A::operator=(const A& 우변_객체){
A *pB = pA; // 먼저 pA 가 가리키는 객체를 pB에 임시로 저장해 놓습니다.
pA = new A(*우변_객체.pA);
delete pB; //동적 할당이 성공적으로 끝나면, 임시 객체를 해제합니다.
return *this;
}
위 예제 코드는 임시 포인터를 만들어 좌변 객체를 가리키는 포인터를 별도로 저장해 놓고, 동적 할당을 진행합니다. 따라서, 예외로 부터 안전한 대입 연산 진행이 가능해집니다!
2. swap
class A {
public:
...
void swap (A& 우변_객체);
...
};
A& A::operator=(const A& 우변_객체){
A temp(우변_객체); // 임시의 A 객체에 우변 객체를 복사 생성자를 통해 복사합니다.
swap(temp);
return *this;
}
두 번째 방법은 "swap"연산을 사용하는 방법입니다. 우변 객체의 복사본을 임시로 생성해 좌변 객체와 swap 연산을 실행합니다. 결과적으로 좌변 객체는 우변 객체의 값을 전달 받고 반환됩니다. 사본을 만드는 방법들을 살펴봤습니다. 우리가 이미 알고 있는 내용으로 "값에 의한 전달"을 수행하는 함수는 해당 객체의 사본을 만든다는 것도 알고 있죠! 값에 의한 전달로 대입 연산자를 작성해도 도움이 됩니다!
중복 참조자에대한 자기 대입 연산을 방지하기 위해 대입 연산자 본문에 일치성 검사, 혹은 사본이나 복사를 통해 텅 빈 객체를 가리키거나, 예외로 부터 안전할 수 있습니다.
'언어 > Effective C++' 카테고리의 다른 글
[Effective C++] #14 자원 관리 객체 심화, RAII, shared_ptr, 삭제자 지정 (0) | 2022.01.14 |
---|---|
[Effective C++] #13 자원 관리 객체, std::auto_ptr, std::shared_ptr (0) | 2022.01.14 |
[Effective C++] #10 대입 연산자와 *this의 참조자 (0) | 2022.01.12 |
[Effective C++] #9 가상 함수를 호출하는 생성자, 소멸자 (0) | 2022.01.10 |
[Effective C++] #8 소멸자가 던지는 예외 (0) | 2022.01.10 |