본문 바로가기
Effective C++

이펙티브(Effective) C++ Item 11 : operator= 에서는 자기 대입에 대한 처리가 빠지지 않도록 하자.

by gong재이 2022. 5. 22.
반응형

자기 대입(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)으로 전달되기 때문에 복사생성자를 통한 복사를 해주지 않아도 된다.

 

반응형

댓글