연산자 오버로딩 (operator overloading)
함수 오버로딩이란 같은 일을 처리하는 함수를 매개변수의 형식을 달리하여 하나의 이름으로 만들 수 있게 해준다.
마찬가지로, 하나의 연산자를 여러 의미로 사용할 수 있게 해주는 게 연산자 오버로딩이다.
연산자 함수 (operator function)
C++에서 연산자를 오버로딩하기 위해서 연산자 함수 (operator function)을 사용한다.
operator연산자(parameter)
연산자 함수는 operator 키워드를 사용해 연산자를 오버로딩하며, 오버로딩할 연산자는 operator 키워드와 공백없이 연결되어야 한다.
● 멤버함수로 오버로딩
# include <iostream>
class Foo
{
private:
int xpos;
int ypos;
public:
Foo(int x=0, int y=0) : xpos(x), ypos(y) { }
void print() { cout << xpos << ", "<< ypos << "\n"; }
};
int main()
{
Foo pos1(10, 20);
Foo pos2(3, 4);
Foo pos3 = pos1 + pos2;
pos3.print();
}
위의 코드를 컴파일하면 에러가 난다.
Foo pos3 = pos1 + pos2;
피연산자 형식이 Foo + Foo인데 이 연산과 일치하는 "+" 연산자가 없기 때문이다.
즉, 연산자 오버로딩을 활용하여 Foo 객체끼리의 덧셈이 가능한 연산자를 만들어줘야 한다.
operator 정의 방법
# include <iostream>
class Foo
{
private:
int xpos;
int ypos;
public:
Foo(int x=0, int y=0) : xpos(x), ypos(y) { }
void print() { cout << xpos << ", "<< ypos << "\n"; }
Foo operator+(const Foo &ref)
{
return Foo(xpos+ref.xpos, ypos+ref.ypos);
}
};
int main()
{
Foo pos1(10, 20);
Foo pos2(3, 4);
// 아래 두 줄은 같은 의미
Foo pos3 = pos1 + pos2;
Foo pos3 = pos1.operator+(pos2);
pos3.print();
}
operator+라는 함수를 추가하였다. 전달받은 객체와 자신의 멤버 변수를 서로 더해 Foo 객체를 만들어 반환한다.
피연산자의 자료형이 서로 달라도 오버로딩이 가능하다. 아래의 코드를 확인해보자.
# include <iostream>
class Foo
{
private:
int xpos;
int ypos;
public:
Foo(int x=0, int y=0) : xpos(x), ypos(y) { }
void print() { cout << xpos << ", "<< ypos << "\n"; }
Foo operator+(int num)
{
return Foo(xpos+num, ypos+num);
}
};
int main()
{
Foo pos1(10, 20);
Foo result = pos1 + 10;
result.print();
}
아래 코드가 추가가 되었는데, 이 코드는 컴파일이 되는 걸 확인해 볼 수 있다.
Foo result = pos1 + 10;
전체 코드에서 operator+함수를 보면 매개변수의 자료형이 정수형으로, 생성자에게 xpos+num, ypos+num을 넘겨주어 초기화를 한 후, result가 출력함을 볼 수 있다.
반면에 아래의 코드는 정상적으로 컴파일 되지 않는다.
Foo result = 10 + pos1;
클래스 내에 멤버 함수를 정의하고, 이 멤버 함수를 통해 연산자 오버로딩을 했다.
pos1+10이라는 형태가 pos1.operator+10인 것처럼 10+pos1는 10.operator+pos1라고 인식된다.
10이라는 정수형 데이터에 operator+란 함수가 정의되어 있지 않고, Foo 객체를 넘기는 형태가 되었기 때문에 정상적인 코드가 아니다.
이 경우에는 전역 함수 오버로딩으로 해결할 수 있다.
● 전역함수로 오버로딩
class Foo
{
private:
int xpos, ypos;
public:
Foo(int x=0, int y=0) : xpos(x), ypos(y) { }
void print() const { cout << xpos << ", " << ypos << endl; }
firend Foo operator+(const Foo &pos1, const Foo &pos2);
};
Foo operator+(const Foo &pos1, const Point &pos2)
{
return Foo(pos1.xpos+pos2.xpos, pos1.ypos+pos2.ypos);
}
int maint()
{
Foo pos1(3, 4);
Foo pos2(10, 20);
Foo pos3=pos1+pos2;
pos1.print();
pos2.print();
pos3.print();
return 0;
}
opertaor+ 함수 앞에 friend 키워드가 붙어있다.
firend Foo operator+(const Foo &pos1, const Foo &pos2);
opertor+ 함수가 클래스의 멤버 함수가 아니라 멤버 변수에 접근할 수 없기 때문에 firend를 통해 private 멤버인 xpos와 ypos에 접근이 가능하다.
Foo pos3=pos1+pos2;
pos1+pos2는 opertaor+ 함수를 통해 operator+(pos1,pos2)로 인식된다.
● 연산자를 오버로딩하는 두 가지 방법
이와 같이, 오버로딩 형태에 따라서 스스로 변환하며 피연산자에 따라서 +연산의 형태가 달라지는 것을 연산자 오버로딩이라고 한다.
오버로딩이 불가능한 연산자
오버로딩 불가능
. | 멤버 접근 연산자 |
.* | 멤버 포인터 연산자 |
:: | 범위 지정 연산자 |
?: | 조건 연산자 (3항 연산자) |
sizeof | 바이트 단위 크기 계싼 |
typeid | RTTI 관련 연산자 |
static_cast | 형변환 연산자 |
dynamic_cast | 형변환 연산자 |
const_cast | 형변환 연산자 |
repinterpret_cast | 형변환 연산자 |
멤버함수 형태로만 오버로딩 가능
= | 대입 연산자 |
() | 함수 호출 연산자 |
[] | 배열 접근 연산자 (인덱스 연산자) |
-> | 멤버 접근을 위한 포인터 연산자 |
● 주의사항
- 본래의 의도를 벗어난 형태의 연산자 오버로딩은 좋지 않다.
- 연산자의 우선순위와 결합성은 바뀌지 않는다.
- 매개변수의 자료형에 따라 호출되는 함수가 결정되므로 매개변수의 Default 값 설정이 불가능하다
- 연산자의 순수 기능까지 빼앗을 수 없다.
int operator+(const int num1, const int num2)
{
retrun num1*num2;
}
위와 같은 형태는 정의가 불가능하다.