5-2. 연산자 오버로딩(이항, 입출력, 타입변환, 증감 연산자)
friend 키워드
friend 키워드는 클래스 내부에서 다른 클래스나 함수들을 frient 로 정의할 수 있는데, friend 로 정의 된 클래스나 함수들은 원래 클래스의 private 로 정의된 변수나 함수들에 접근할 수 있다.
아래 코드에서 클래스 B 와 void func() 는 A 의 friend 라고 선언하고 있다. 이렇게 friend 라고 선언하게 되면, B 와 func 안에서는 A 의 모든 private 멤버 함수들과 멤버 변수들에 대한 접근 권한을 부여하게 된다. -> 정말 친한 친구 사이라고 보면 된다.
한가지 재미있는 점은 짝사랑과 같다는 점이다. -> 아래 코드에서 B 는 A 의 모든 private 들을 볼 수 있지만, B 안에서 A 를 friend 로 지정하지 않는 이상, A 는 B의 private 개체들에 접근할 수 없다.
class A {
private:
void private_func() {}
int private_num;
// B 는 A 의 친구!
friend class B;
// func 은 A 의 친구!
friend void func();
};
class B {
public:
void b() {
A a;
// 비록 private 함수의 필드들이지만 친구이기 때문에 접근 가능하다.
a.private_func();
a.private_num = 2;
}
};
void func() {
A a;
// 비록 private 함수의 필드들이지만 위와 마찬가지로 친구이기 때문에 접근
// 가능하다.
a.private_func();
a.private_num = 2;
}
int main() {}이항 연산자 오버로딩
아래 이항 연산자 오버로딩에는 다음과 같은 문제점이 있다.
1 은 정상적으로 컴파일 되지만,
2 는 컴파일되지 않는다.
왜냐면 1 은 a.operator+("i3.923"); 로 변환될 수 있지만, 2 는 그렇지 못하기 때문이다.
하지만 개발자 입장에서는 원칙적으로 클래스를 사용하는 입장에서 1 이 된다면 2 도 되어야 한다.
다행스럽게, 컴파일러는 이항 연산자를 다음과 같은 두 개의 방식으로 해석한다.
컴파일러는 둘 중 가능한 케이스를 택해서 처리한다.
a.operator+(b) 에서의 operator+ 는 a 의 클래스 멤버 함수로써 사용되는 것이고,
operator+(a, b) 에서의 operator+ 는 클래스 외부에 정의되어 있는 일반적인 함수를 의미하게 된다.
이를 처리하기 위해서 함수를 정의하면 다음과 같다. -> 두 개의 const Complex& 타입의 인자 a,b 를 받는 함수를 구현했다.
앞에서 언급했던 것과 같이 컴파일러는 정확히 타입이 일치하지 않는 경우, 가장 가까운 가능한 오버로딩 되는 함수를 찾게 되는데, 마침 우리에게는 Complex(const char * ) 타입의 생성자가 있다면 아래와 같이 타입 변환이 가능하게 된다.
구현된 코드는 다음과 같다. -> Complex 클래스 내부에 friend Complex operator+(const Complex& a, const Complex& b); 선언을 한다.
위 코드에서 모든 문제가 해결된 것은 아니다.
아래 실행 코드에서는, 다음과 같이 두가지 연산자 중 어떤 연산자를 사용할 지 모르겠다는 오류가 발생한다.

앞서 컴파일러는 a + a 를 해석할 때 아래와 같이 두가지 형태 중 하나를 선택하려 한다는 것이다.
이를 해결하기 위해서는 둘 중 하나의 함수를 없애야 한다.
기본적으로, 자기 자신을 리턴하지 않는 이항 연산자들, 예를 들어 +, -, *, / 들은 모두 외부 함수로 선언하는 것 이 원칙이다.
반대로, 자기 자신을 리턴하는 이항 연산자들, 예를 들어 +=, -= 들은 모두 멤버 함수로 선언하는 것이 원칙이다.
위 원칙을 적용한 코드는 다음과 같다.
입출력 연산자 오버로딩
아래 코드는 std::cout.operator<<(a) 와 같은 의미이다.
실제로 우리가 include 하는 iostream 의 헤더파일을 살펴보면 ostream 클래스에 다음과 같이 엄청난 수의 operator<< 가 정의되어 있는 것을 알 수 있다.
우리의 Complex 클래스에서도 operator<< 연산자를 오버로딩 할 수 있다. -> 하지만 ostream 은 표준 헤더파일의 내용을 수정하는 것은 불가하기 떄문에 다른 방법을 사용해야 한다.
우리는 클래스에 연산자 함수를 추가하는 방법으로, 멤버 함수를 사용하는 것 이외에도 한 가지 더 있다는 것을 알고 있다. 바로 ostream 클래스 개체와 Complex 개체 두 개를 인자로 받는 전역 operator<< 함수를 정의하면 된다.
하지만 위 operator<< 는 문제가 있는데, 해당 operator<< 에서 c.real, c.img 에 접근할 수 없다는 점이다.
따라서 위 operator<< 를 friend 로 지정하면 된다.
완성된 코드는 다음과 같다.
무분별한 friend 키워드는 지양하자물론 무분별하게
friend키워드를 남발하는 것은 썩 권장하지 않는다. 왜냐하면friend키워드는 해당 함수나 클래스에게 자기 자신의 모든private멤버 함수와 변수들을 공개하기 때문이다. 따라서 구현 디테일은 최대한 숨기라 는 원칙을 지키기가 힘들어진다. 물론 절대friend를 쓰지 말라는 것은 아니고, 테스트 코드 작성과 같이friend를 유용하게 사용할 수 있는 경우가 종종 있다.
타입변환 연산자 오버로딩 - Wrapper 클래스
Wrapper 클래스는 기본 자료형들을 클래스로 포장해 각각의 자료형을 개체로 사용하는 것을 Wrapper 클래스라고 부른다.
int 자료형을 감싸는 int Wrapper 클래스 Int 는 다음과 같이 구성할 수 있다.
위 Wrapper 클래스에서 아래 코드가 작성하기 위해서 int 자료형에서 사용되는 모든 연산자 함수를 만들어주면 된다고 생각할 수 있다. -> 하지만 수많은 연산자 오버로딩을 하는 것은 매우 괴롭다..
그렇다면 그냥 이 Wrapper 클래스 개체를 마치 int 형 변수라고 컴파일러가 생각하게 하는 방법이 있을까?
물론 있다! -> 왜냐면 우리에게는 타입 변환 연산자가 있기 때문이다.
타입 변환 연산자는 다음과 같이 정의한다.
예를 들어서 우리의 Wrapper 클래스의 경우 다음과 같은 타입 변환 연산자를 정의할 수 있다.
한가지 주의할 점은, 함수의 리턴 타입을 사용하면 안된다! -> 이는 C++ 에서 변환 연산자를 정의하기 위한 규칙이다.
아래와 같이 코드를 완성할 수 있다. -> 컴파일러 입장에서 적절한 변환 연산자로 operator int 를 찾아서 결국 int 로 변환해서 처리하게 된다.
증감 연산자 오버로딩
증감 연산자는 전위와 후위로 나누어지는데, 연산자 오버로딩 시 어떻게 구분할까?
전위 증감 연산자 오버로딩
후위 증감 연산자 오버로딩
여기서 매개변수 x 는 아무런 의미가 없다. 단순히 컴파일러가 전위와 후위를 구별하기 위해서 int 인자를 넣어주는 것 뿐이다.
주의할 점
여기서 주의할 점은 전위 증감 연산의 경우 값이 바뀐 자신을 리턴해야 하고,
후위 증감 연산의 경우 값이 바뀌기 이전의 개체를 리턴해야 한다.
증감 연산자 오버로딩의 예제는 다음과 같다.
Last updated