Game Develop

operator= (대입연산자) 에서 자기대입에 대한 처리가 빠지지 않도록 하자. 본문

C++/Effective C++

operator= (대입연산자) 에서 자기대입에 대한 처리가 빠지지 않도록 하자.

MaxLevel 2025. 11. 12. 23:14

 

여기서 말하는 자기대입(self assignment)란, 어떤 객체가 자기 자신에 대해 대입연산자를 적용하는 것을 의미한다.

대충 아래처럼?

 

Widget w;

w = w;

 

이게 왜 위험한가... 아래의 예시코드를 먼저 보자.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
 
class Bitmap {};
 
class Widget
{
 
public:
    Widget& operator= (const Widget& rhs)
    {
        delete pb;
        pb = new Bitmap(*rhs.pb);
 
        return *this;
    }
 
private:
    Bitmap* pb;
};
cs

 

 

위의 예시코드는 안전하지 않은 대입연산자 코드이다.

왜? rhs가 자기자신일 수 있으니까. 맨 처음의 w = w를 생각해면 된다.

그러면 Bitmap 포인터변수가 같은 놈이라는건데, 대입연산자 첫줄에 delete pb를 해버린게 보이는가?

그러면 rhs의 pb도 delete 됐다는 것이다.

그 상태에서 rhs의 pb를 이용해서 메모리할당을 시도하니 문제가 생길 수 밖에 없다.

 

이런 현상을 막기위한 제일 쉬운 방법은 자신과 rhs를 비교해서, 다를때만 대입연산자를 시도하는 것.

 

if (this == &rhs) return *this;    // 이 코드 한줄만 앞에 추가해주면 된다.

 

1
2
3
4
5
6
7
8
9
10
11
12
    Widget& operator= (const Widget& rhs)
    {
        if (this == &rhs)
        {
            return *this;
        }
        
        delete pb;
        pb = new Bitmap(*rhs.pb);
 
        return *this;
    }
cs

 

이런식으로.

 

근데 이 코드도 안전하지 않은 부분이 있다.

new Bitmap부분에서 예외가 발생해버리는 경우다. (뭐 동적할당 메모리가 부족하다던지, *rhs.pb를 이용한 Bitmap의 복사생성 자체에서 예외가 터져버린다던지...)

 

예외가 발생해버리면 그냥 pb만 delete해버린 꼴이다. 이런 객체가 남겨져 있으면 어떤 예상치 못한 상황이 발생할 지 알 수가 없다.

댕글링 포인터가 된 셈.

 

그러니 예외가 발생할 경우를 대비해, 무작정 삭제한다음에 새로 할당하려하지 말고 혹시 모르니 기존값을 미리 저장 후, 새로 할당받고 기존값을 삭제하는 흐름으로 진행하면 안전하다.

 

 
1
2
3
4
5
6
7
8
Widget& operator= (const Widget& rhs)
{
    Bitmap* pOrig = pb;
    pb = new Bitmap(*rhs.pb);
    delete pOrig;
    
    return *this;
}
cs

 

 

이런식으로...

 

보면 기존값을 pOrig라는 새로운 지역변수에 미리 저장한다음 동적메모리할당을 시도한다.

예외가 발생하더라도 pb는 기존값을 유지하고 있기 때문에 이전의 방법보다 더 안전하다.

그리고 if문으로 자기자신인지 검사할 필요도 없다. 원본 pb로 새로 할당받고, 기존꺼는 지역변수에 보관한다음 그걸 delete 하니까.