본문 바로가기

Programming/C / C++

[C++] 대입 연산자 오버로딩

 대입 연산자  
 위와 같은 문장이 있다. 여기서 b는 A클래스의 b객체이다. 이 문장은 다음과 같이 묵시적으로 변환이 일어난다. A a(b); 결국 a라는 이름으로 객체 생성할때 복사 생성자를 호출 하는 형태가 된다. 그럼 다음과 같은 문장은 어떤 일이 일어날까?
 여기서 a = b; 이 문장은 a(b) 이런 형태로 묵시적 변환이 일어 나지 않는다. 위와 같은 대입연산을 썼지만, 객체 생성문장이 아니기 때문이다. 그럼 여기서 a = b;는 다음과 같이 해석 될 것이다. a.operator=(b); 복사생성에서 호출되는 대입연산과 오버로딩 되어 있는 operator 함수 호출에서의 대입연산을 혼동하지 말자

 대입 연산자 오버로딩  

  위에서 말한것과 같이, 31번째 라인에서는 a.operator=(b); 이런 형태로 대입연산 오버로딩이 일어날 것입니다. 하지만 여기 소스코드에서는 어디에서도 대입 연산을 정의 하는 함수를 찾아 볼 수 없다. 그것은 바로 대엽 연산자 오버로딩에 대해서만 "디폴트 대입 연산자"가 존재하기 때문이다. 그래서 우리가 명시적으로 써주지 않아도 이 디폴트 대입 연산자를 호출해 문제가 발생하지 않는 것이다. 
 만약 우리가 정의 한다고 하면 아래와 같이 정의 할 수 있겠다. 
 왜 참조형으로 리턴을 하냐면, 대입 연산도 p1 = p2 = p3; 이런 형태의 대입연산도 쓰기 때문에, 이것을 감안해서 참조형으로 리턴을 해줘야 한다. 그럼 이렇게 디폴트 대입 연산자가 있는데 굳이 우리가 정의를 해서 쓸 일이 있을까? 반드시 정의를 해서 써야 하는 경우가 있는데, 그 경우가 바로 깊은 복사(Deep Copy)가 요구 되어 지는 경우이다. 이와 같이 디폴트 대입 연산자의 문제점을 알아 볼 수 있다. 

 문제점  
 위의 소스 코드를 실행해 보면 컴파일에는 문제가 없지만, 런타임에서 문제가 생긴다. 그럼 이런 문제는 왜 일어 나는 것일까?
 여기서는 Person 클래스의 p1과 p2 객체를 생성하는데 p1과 p2는 객채 내에 name이라는 포인터가 있어서 문자열의 길이 만큼 동적 할당을하고 이 저장한 공간을  p1 객체가 포인터를 유지하는 형태를 띠게 되는데, 동적할당은 Heap 영역에 p1과 p2객체는 스택에 생성이 된다.
 그 후, 40번째 라인의 p1 = p2; 은 p1.operator=(p2) 이것으로 해석 되므로, 대입 연산자 오버로딩이 일어 날 것이다. 우선 여기서는 대입 연산자가 따로 정의 되어 있지 않으므로, 디폴트 대입 연산자를 사용해서 p2가 인자로 전달되어 멤버간의 복사가 일어 날 것이다. (p2가 지니는 값을 p1에 복사하는 것이다.) 하지만 p2의 멤버는 객체의 포인터이다. 그래서 객체의 포인터 값을 p1에다 복사 할텐데, 처음 가리키고던 "One"이 끊어지고 p1은 "Two"를 가리키게 된다. 이게 바로 문제다. 
 이 문제는 복사 생성자에서도 생겼던 문제 인데, 하나의 문자열을 두개의 객체가 참조 하다 보니까 소멸자 호출에 있어서 문제가 생기고,(이것은 복사생성자에서도 생겼던 문제) 포인터를 잃어 버렸기 때문에 프로그램 종료될때까지 메모리 공간에서 메모리를 잡아 먹게 되어 메모리 유출이 발생하는 문제가 발생된다. 그래서 이 경우에는 대입 연산자를 꼭 재정의 해줘야 한다. 
 일단 포인터가 끊어지고 생기는 메모리 유출에 대한 문제를 해결하고, 메모리 공간을 다시 할당해 복사를 해주자. 아래는 새로 정의된 대입 연산자 함수와 결과 출력이다. 이제는 문제 없이 출력되는 것을 알 수 있다.