Post List

2015년 1월 4일 일요일

operator= 에서는 자기대입에 대한 처리가 빠지지 않도록 하자.

* operator=을 구현할 때, 어떤 객체가 그 자신에 대입되는 경우를 제대로 처리하도록 만듭시다. 원본 객체와 복사대상 객체의 주소를 비교해도 되고, 문장의 순서를 적절히 조정할 수도 있으며, 복사 후 맞바꾸기 기법을 써도 됩니다.

w = w;       // 당연히 자기 자신에 대한 대입
a[i] = a[j]; // i = j 라면 ???
*px = *py;   // px 와 py가 같은 Object에 대한 Pointer라면 ???

위의 경우는 자기대입의 가능성이 있는 Code 들이다. 아래의 Code와 같이 대입 연산자를 정의하면 자기대입을 할 경우 문제가 발생한다.

class Widget {
public:
    ...
    Widget& operator= (const Widget& rhs)
    {
        delete pb;
        pb = new Bitmap(*rhs.pb);
        return *this;
    }
private:
    Bitmap *pb;
};

자기 자신에 대한 대입 연산자를 실행하게 되면 pb가 가리키는 객체가 삭제된다. 해결하는 방법은 3가지가 있다.

첫째, 객체의 주소를 비교

class Widget {
public:
    ...
    Widget& operator= (const Widget& rhs)
    {
        if (this = &rhs) return *this;

        delete pb;
        pb = new Bitmap(*rhs.pb);

        return *this;
    }
private:
    Bitmap *pb;
};

하지만 new Bitmap에서 예외가 터지면 pb는 결국 삭제된 Bitmap을 가리키는 포인터를 껴안고 홀로 남게 된다. 하지만 많은 경우에 문장 순서를 세심하게 바꾸는 것만으로 예외에 안전한 코드가 만들어진다.

둘째, 문장의 순서를 적절히 조정

class Widget {
public:
    ...
    Widget& operator= (const Widget& rhs)
    {
        Bitmap *pOld = pb;
        pb = new Bitmap(*rhs.pb);
        delete pOld;

        return *this;
    }
private:
    Bitmap *pb;
};

이 방법으로 자기대입또한 효율적으로 처리 된다. 하지만, 앞에 주소비교가 빠져서 찝찝한가 ? 자기대입이 과연 얼마나 자주 일어날까 ? if로 비교하는건 뭐 꽁짠가 ?

셋째, 복사 후 맞바꾸기 (copy and swap)

class Widget {
public:
    ...
    void swap(Widget& rhs) { ... };
    Widget& operator= (const Widget& rhs)
    {
        Widget temp(ths);
        swap(temp);
        return *this;
    }
private:
    Bitmap *pb;
};

왠지 좀 temp를 사용하는게 비효율적으로 보이는가 ? 그럼 아래와 같이 고쳐볼까 ?

Widget& Widget::operator= (const Widget rhs)
{
    swap(rhs);
    return *this;
}

이건 뭐 복사연산이 안일어나나 ? parameter로 올때 이미 복사가 되었구만. '솜씨'라는 이름의 제단에서 명확성을 제물로 바치는 격이다. 이러지 말자.

* 두 개 이상의 객체에 대해 동작하는 함수가 있다면, 이 함수에 넘겨지는 객체들이 사실 같은 객체인 경우에 정확하게 동작하는지 확인해 보세요.


class Base { ... };
class Derived : public Base { ... };
void doSomething (const Base& rb, Derived* pd); // rb 와 pd가 같은 Object 일수도 있다.

댓글 없음:

댓글 쓰기