C++의 한가지 멋진 기능 중 하나는 클래스를 선언할 때 연산자에 특별한 의미를 부여할 수 있다는 것이다. 이것은 operator overloading
이라고 부른다. 아래 나열되는 특정 네이밍 컨벤션을 따라 클래스에 멤버함수를 선언함으로서 C++ operator overload를 구현할 수 있다. 예를들어 +
연산자를 오버로딩하기 위해서 클래스에 operator+
라는 이름의 멤버함수를 만들어주면 된다.
일반적으로 오버로딩에 많이 쓰이는 연산자는 다음과 같다.
=
(대입 연산자)+
, -
, *
(이진 산술 연산자)+=
, -=
, *=
(복합 대입 연산자)==
, !=
(비교 연산자)대입 연산자는 다음과 같이 구현한다.
class MyClass {
public:
...
MyClass & operator=(const MyClass &rhs);
...
}
MyClass a, b;
...
b = a; // b.operator=(a); 와 같은 의미
=
연산자는 대입의 우항에 상수참조를 받는 것을 확인할 수 있다. 우항은 변경되지 않고 오직 좌항만 변경하는 것을 보장하도록 구현하기 위함이다.
또한, 연산자 반환은 참조로 하는 것을 확인할 수 있다. 이는 다음과 같이 operator chaining
을 허용하기 위함이다.
int a, b, c, d, e;
a = b = c = d = e = 42;
이는 컴파일러에 의해 다음과 같이 해석된다.
a = (b = (c = (d = (e = 42))));
다르게 말해서, 대입(할당)은 우측결합이다. 마지막 대입 연산을 먼저 계산하고, 좌측으로 연산하며 값이 전달되는 방식이다.
이 operator chaining
을 지원하기 위해 대입 연산자는 반드시 어떤 값을 반환해야 한다. 반드시 반환되어야 하는 값은 좌항의 참조이다.
여기서 반환하는 참조는 const
로 선언되어서는 안된다. 왜냐하면 다음과 같은 코드도 작성 가능하기 때문이다.
MyClass a, b, c;
...
(a = b) = c; // What??
따라서 가상의 MyClass
의 대입 연산자를 다음과 같이 구현할 수 있다.
// Take a const-reference to the right-hand side of the assignment.
// Return a non-const reference to the left-hand side.
MyClass& MyClass::operator=(const MyClass &rhs) {
... // Do the assignment operation!
return *this; // Return a reference to myself.
}
this
는 멤버함수가 호출되고 있는 오브젝트의 포인터이다. a = b
는 a.operator=(b)
로 간주할 수 있다. 따라서 참조자를 반환해야 하므로 *this
를 반환한다.
새로 정의하는 클래스 내에서 메모리할당을 한다면 중요한 것을 기억해야 한다. 일반적으로 대입연산자 함수 내에서는 다음과 같은 흐름으로 동작한다.
MyClass& MyClass::operator=(const MyClass &rhs) {
// 1. MyClass 내에서 사용중인 메모리를 해제하고
// 2. 대입해오는 우항의 값을 저장할 메모리를 새롭게 할당한다.
// 3. 우항의 값을 새로 할당한 메모리 공간에 복사한다.
// 4. *this 를 반환한다.
}
그런데 아래와 같은 코드를 작성한다면 어떻게 동작할까?
MyClass mc;
...
mc = mc; // 헉
이 코드가 실행되는 순간 대혼란이 발생할 것이다. 왜냐하면 mc는 좌항이면서 우항이므로 좌항에서 메모리를 해제한 순간 복사할 원본의 메모리도 해제가 된 것이다. 따라서 어떤 값이 복사될지 알 수 없다.
여기서 자기할당을 체크하는 방법을 알아보자. 두 인스턴스가 완전히 동일한가 체크할 필요없이 두 객체의 주소가 같은지를 보면 된다. 주소가 같다면 대입을 하지 않으면 된다.
수정된 안전한 버전의 MyClass
클래스를 살펴보자.
MyClass& MyClass::operator=(const MyClass &rhs) {
// Check for self-assignment!
if (this == &rhs) // Same object?
return *this; // Yes, so skip assignment, and just return *this.
... // Deallocate, allocate new space, copy values...
return *this;
}
정리해보자.
1. 매개변수(우항)로 상수 참조를 취하라.
2. operator chaining
을 지원하기 위해 좌항의 참조를 반환하라. (*this
)
3. 포인터를 비교하여 자기할당을 체크하라. (this
vs &rhs
)
MyClass a, b;
...
a += b; // Same as a.operator+=(b)
위와 같이 복합 할당 연산을 수행할 수 있다.
대입 연산자와 마찬가지로 다음과 같이 참조를 반환하도록 한다.
MyClass & MyClass::operator+=(const MyClass &rhs) {
... // Do the compound assignment work.
return *this;
}
복합 할당 연산자가 구현되어 있는 경우 복합 할당 연산자를 이용해서 이진 산술 연산자를 구현할 수 있다.
// Add this instance's value to other, and return a new instance
// with the result.
const MyClass MyClass::operator+(const MyClass &other) const {
MyClass result = *this; // Make a copy of myself. Same as MyClass result(*this);
result += other; // Use += to add other to the copy.
return result; // All done!
}
조금 더 심플하게 구현한다면 다음과 같다.
// 인스턴스의 값을 다른 곳에 추가하고 결과와 함께 새 인스턴스를 반환합니다.
const MyClass MyClass::operator+(const MyClass &other) const {
return MyClass(*this) += other;
}