함수가 오버로딩 되면, 오버로딩 된 수 만큼 다양한 기능을 제공하게 된다.
연산자도 마찬가지로 오버로딩을 통해 기존에 존재하던 연산자의 기본 기능 이외에 다른 기능을 추가할 수 있다.
operator 키워드와 연산자를 묶어서 함수의 이름을 정의하면, 함수의 이름을 이용한 호출뿐만 아니라, 연산자를 이용한 함수의 호출도 허용해 준다.
(반환값) operator(연산자) (인자)
다음과 같이 좌표를 나타내는 클래스 Point 가 있다고 하자.
class Point
{
public:
Point(int Y, int X) : PosY(Y), PosX(X) {};
private:
int PosX = 0;
int PosY = 0;
};
이 Point 객체에 대해 두 좌표값을 비교할 수 있도록 비교 연산자를 오버로딩 하겠다.
멤버 함수를 이용해 다음과 같이 연산자 오버로딩을 할 수 있다.
#include <iostream>
class Point
{
public:
Point(int Y, int X) : PosY(Y), PosX(X) {};
bool operator==(const Point& _Other)
{
return PosY == _Other.PosY && PosX == _Other.PosX;
}
private:
int PosX = 0;
int PosY = 0;
};
int main()
{
Point Pos1 = { 1, 2 };
Point Pos2 = { 2, 2 };
Point Pos3 = { 1, 2 };
std::cout << (Pos1 == Pos2) << std::endl; // false
std::cout << (Pos1 == Pos3) << std::endl; // true
std::cout << (Pos2 == Pos3) << std::endl; // false
return 0;
}
0
1
0
위의 코드에서 Pos1 == Pos2 는 Pos1.operator==(Pos2) 로 해석되어서 컴파일 된다.
전역 함수를 이용해 다음과 같이 연산자 오버로딩을 할 수 있다.
#include <iostream>
class Point
{
public:
Point(int Y, int X) : PosY(Y), PosX(X) {};
friend bool operator==(const Point& _Pos1, const Point& _Pos2);
private:
int PosX = 0;
int PosY = 0;
};
bool operator==(const Point& _Pos1, const Point& _Pos2)
{
return _Pos1.PosY == _Pos2.PosY && _Pos1.PosX == _Pos2.PosX;
}
int main()
{
Point Pos1 = { 1, 2 };
Point Pos2 = { 2, 2 };
Point Pos3 = { 1, 2 };
std::cout << (Pos1 == Pos2) << std::endl; // false
std::cout << (Pos1 == Pos3) << std::endl; // true
std::cout << (Pos2 == Pos3) << std::endl; // false
return 0;
}
0
1
0
위의 코드에서 Pos1 == Pos2 는 operator==(Pos1, Pos2) 로 해석되어서 컴파일 된다.
C++은 C스타일의 코드 구현이 가능한 언어이기 때문에 ‘전역(Global)’에 대한 개념이 존재하지만, 원래 객체지향에는 전역이란 개념이 존재하지 않는다. 따라서 특별한 경우가 아니면 멤버 함수를 기반으로 연산자 오버로딩을 하는 게 낫다.
C++의 모든 연산자들이 오버로딩의 대상이 되는것은 아니다. C++의 문법 규칙을 보존하기 위해 다음과 같은 연산자는 오버로딩이 불가능하다.
. |
멤버 접근 연산자 |
|---|---|
.* |
멤버 포인터 연산자 |
:: |
범위 지정 연산자 |
?: |
조건 연산자 (3항 연산자) |
sizeof |
바이트 단위 크기 계산 연산자 |
typeid |
RTTI 관련 연산자 |
static_cast |
형변환 연산자 |
dynamic_cast |
형변환 연산자 |
const_cast |
형변환 연산자 |
reinterpret_cast |
형변환 연산자 |
아래는 멤버 함수 기반으로만 오버로딩이 가능한 연산자이다.
= |
대입 연산자 |
|---|---|
() |
함수 호출 연산자 |
[] |
배열 접근 연산자 (인덱스 연산자) |
-> |
멤버 접근을 위한 포인터 연산자 |
증감 연산자인 ++, -- 연산자는 단항 연산자이며 피연산자의 위치에 따라서 의미가 달라진다.
전위 증감과 후위 증감 연산자의 오버로딩은 int 키워드를 이용해 구분한다.
#include <iostream>
class Point
{
public:
Point(int Y, int X) : PosY(Y), PosX(X) {};
void ShowPosition() const
{
std::cout << '[' << PosY << ", " << PosX << ']' << std::endl;
}
Point& operator++() // 전위 증가
{
++PosY; ++PosX;
return *this;
}
const Point operator++(int) // 후위 증가
{
Point Result(PosY, PosX);
++PosY; ++PosX;
return Result;
}
Point& operator--() // 전위 감소
{
--PosY; --PosX;
return *this;
}
const Point operator--(int) // 후위 감소
{
Point Result(PosY, PosX);
--PosY; --PosX;
return Result;
}
private:
int PosX = 0;
int PosY = 0;
};
int main()
{
Point Pos1 = { 2, 1 };
Point Tmp = Pos1;
(Pos1++).ShowPosition();
(Pos1).ShowPosition();
(++Pos1).ShowPosition();
return 0;
}
[2, 1]
[3, 2]
[4, 3]
교환 법칙이란 ‘A + B 의 결과는 B + A 의 결과와 같음’을 의미하며 대표적으로 교환법칙이 성립하는 연산으로는 덧셈, 곱셈 연산이 있다.
연산자 오버로딩의 경우에도 이러한 교환 법칙이 성립해야 보다 자연스러운 연산문 구성이 가능해 질것이다. 이럴때 전역함수를 통한 연산자 오버로딩으로 교환 법칙을 성립시킬 수 있다.
#include <iostream>
class Point
{
public:
Point(int Y = 0, int X = 0) : PosY(Y), PosX(X) {};
void ShowPosition() const
{
std::cout << '[' << PosY << ", " << PosX << ']' << std::endl;
}
Point operator*(int Times)
{
Point Result(PosY * Times, PosX * Times);
return Result;
}
friend Point operator*(int Times, Point& Ref);
private:
int PosX = 0;
int PosY = 0;
};
Point operator*(int Times, Point& Ref)
{
return Ref * Times;
}
int main()
{
Point Pos1 = { 2, 1 };
Point Temp;
Temp = Pos1 * 3;
Temp.ShowPosition();
Temp = 3 * Pos1;
Temp.ShowPosition();
Temp = 2 * Pos1 * 3;
Temp.ShowPosition();
return 0;
}
[6, 3]
[6, 3]
[12, 6]
<<, >> 연산자 오버로딩<<, >> 연산자 오버로딩을 통해 클래스를 대상으로 다음과 같은 유형의 연산을 가능하게 할 수 있다.
int main()
{
Point Pos(3, 4);
std::cout << Pos << std::endl; // [3, 4] 출력
}
이때 cout 은 ostream 클래스의 객체이고, ostream 클래스는 정정할 수 없으므로, 전역 함수를 통한 연산자 오버로딩을 해야한다.
#include <iostream>
class Point
{
public:
Point(int Y = 0, int X = 0) : PosY(Y), PosX(X) {};
friend std::ostream& operator<<(std::ostream& Os, const Point& Pos);
private:
int PosX = 0;
int PosY = 0;
};
std::ostream& operator<<(std::ostream& Os, const Point& Pos)
{
Os << '[' << Pos.PosY << ", " << Pos.PosX << ']' << std::endl;
return Os;
}
int main()
{
Point Pos1 = { 2, 1 };
std::cout << Pos1;
Point Pos2 = { 5, 5 };
Point Pos3 = { 7, 9 };
std::cout << Pos2 << Pos3;
return 0;
}
[2, 1]
[5, 5]
[7, 9]
[] 연산자 오버로딩[] 연산자는 연산의 기본 특성상 멤버 함수 기반으로만 오버로딩 하도록 제한되어 있다.
#include <iostream>
class MyArray
{
public:
MyArray(int Len) : Length(Len)
{
Arr = new int[Len];
}
~MyArray()
{
delete[] Arr;
}
int& operator[](int _Index)
{
if (_Index < 0 || _Index >= Length)
{
std::cout << "Out of Range!" << std::endl;
exit(1);
}
return Arr[_Index];
}
//friend int& operator[](const MyArray& _Arr, int _Index); // 에러!
private:
int* Arr = nullptr;
int Length = 0;
};
int main()
{
MyArray Arr(5);
for (int i = 0; i < 5; ++i)
Arr[i] = i;
for (int i = 0; i < 10; ++i)
std::cout << Arr[i] << " ";
return 0;
}
0 1 2 3 4 Out of Range!