C++는 컴파일 타임 다형성(compile-time polymorphism)을 지원하는 언어입니다. 컴파일 타임 다형성의 대표적인 예로는 함수 오버로딩(function overloading)과 연산자 오버로딩(operator overloading)이 있습니다.
연산자 오버로딩은 기존 연산자를 사용자 정의 타입에 맞게 재정의할 수 있게 해주는 강력한 기능이지만, 자유롭게 사용할 수 있는 것은 아닙니다. C++에서 연산자 오버로딩을 사용할 때 반드시 지켜야 하는 규칙들이 존재합니다. 아래에서 그 규칙들을 하나씩 살펴보겠습니다.
C++ 연산자 오버로딩의 주요 규칙
1. 내장 연산자만 오버로딩할 수 있습니다
오버로딩이 가능한 연산자는 C++에 이미 존재하는 내장(built-in) 연산자뿐입니다. 따라서 C++에 없는 새로운 연산자(예: 거듭제곱 연산자 '**')를 임의로 만들어 오버로딩하는 것은 불가능합니다.
2. 연산자의 항(arity) 수는 변경할 수 없습니다
각 연산자가 원래 가지고 있는 피연산자의 개수를 바꿀 수 없습니다. 예를 들어 단항 연산자인 '!'를 이항 연산자처럼 재정의하거나, 이항 연산자인 '+'를 단항 연산자로 바꾸는 것은 허용되지 않습니다.
3. 연산자의 우선순위는 그대로 유지됩니다
오버로딩된 연산자라도 원래 연산자가 가진 우선순위(precedence)와 결합 법칙은 변경되지 않습니다. 예를 들어 '*'와 '+'를 오버로딩하더라도 곱셈이 덧셈보다 먼저 평가되는 기본 규칙은 그대로 적용됩니다.
4. 함수 호출 연산자를 제외하면 기본 매개변수를 사용할 수 없습니다
오버로딩한 연산자 함수에는 기본 매개변수(default parameter)를 지정할 수 없습니다. 유일한 예외는 함수 호출 연산자 '()'입니다. 이 연산자만은 기본 인자를 허용합니다.
5. 기본 데이터 타입에 대해서는 오버로딩할 수 없습니다
int, float 같은 기본 데이터 타입(primitive type) 간의 연산 동작은 변경할 수 없습니다. 연산자 오버로딩이 성립하려면 피연산자 중 최소 하나 이상이 사용자 정의 데이터 타입(user-defined type), 즉 클래스나 구조체여야 합니다.
6. 일부 연산자는 반드시 멤버 함수로 정의해야 합니다
다음 네 가지 연산자는 friend 함수가 아닌 멤버 함수(member function)로만 오버로딩할 수 있습니다.
- 대입 연산자 '='
- 첨자(subscript) 연산자 '[]'
- 함수 호출 연산자 '()'
- 화살표(멤버 접근) 연산자 '->'
이는 해당 연산자들의 의미적 특성상 첫 번째 피연산자가 반드시 해당 클래스의 객체여야 하기 때문입니다.
7. 일부 연산자는 이미 기본적으로 오버로딩되어 있습니다
대입 연산자 '=', 주소 연산자 '&', 콤마 연산자 ','는 별도로 정의하지 않아도 기본적으로 오버로딩되어 있습니다. 예를 들어 사용자가 대입 연산자를 직접 정의하지 않으면, 컴파일러가 멤버별 얕은 복사(shallow copy)를 수행하는 기본 대입 연산자를 자동으로 생성합니다.
마무리
연산자 오버로딩은 코드의 가독성과 직관성을 높여주는 유용한 기능이지만, 위의 규칙들을 위반하면 컴파일 오류가 발생합니다. 특히 '=', '[]', '()', '->'의 멤버 함수 강제 규칙과 기본 타입 오버로딩 불가 규칙은 실무에서 자주 마주치는 부분이므로 반드시 숙지해 두는 것이 좋습니다.