강께르의 개발일지

[C++] 연산자 오버로딩에 대하여_1 본문

프로그래밍/C++

[C++] 연산자 오버로딩에 대하여_1

강께르 2021. 6. 16. 23:46

1. 연산자 오버로딩이란?

- 오버로딩은 함수의 이름은 같지만 매개변수의 자료형과 갯수에 따라 몸체를 다르게 정의하는 것을 의미한다.

- 그럼 연산자 오버로딩은 무엇일까? 연산자가 숫자 계산으로 사용하던 기본적인 기능 말고 오버로딩하여서 다른 자료형 매개변수에 따라 다른 몸체를 정의하는 것이라고 이야기할 수 있겠다.

class Point
{
private:
	int xPos;
	int yPos;
public:
	Point(int x = 0, int y = 0)
		: xPos(x), yPos(y)
	{ }

	Point operator+(const Point& ref)
	{
		Point pos(xPos + ref.xPos, yPos + ref.yPos);
		return pos;
	}
};

Point pos1(3, 4);
Point pos2(4, 5);
Point pos3 = pos1.operator+(pos2);
Point pos3 = pos1 + pos2;

- 위의 코드를 살펴보자. 위를 이해하고 마지막 두 줄을 보자.

- 끝에서 두 번째 줄은 pos1이라는 객체가 operator+라는 멤버함수를 불러 pos2를 매개변수로 전달하는 모습처럼 보인다. 이것은 익숙한 멤버함수 사용하는 모습이니 넘어가자.

- 그런데 맨 아랫 줄에 pos1 + pos2와 같은 간결한 문장이 나온다. 맥락상 operator+로 인해 가능한 것 같은데 이게 가능한 것일까?

- 열혈 프로그래밍 C++에 의하면 다음과 같다. C++은 객체 지향 프로그래밍을 밀어주는 언어로 객체도 기본 자료형처럼 연산이 가능하게끔 열어두었다는 것이다. 마치 객체가 멤버함수를 호출하여 매개변수를 전달하는 모양을 연산자로 바꿔서 쓰더라도 컴파일러가 그것을 알아서 해석하게끔 해주겠다는 것이다.

- 그래서 연산자 오버로딩은 다음과 같이 이야기할 수 있다. 기본 자료형만을 처리하던 연산자의 기능과 더불어 객체에 대한 처리 기능을 오버로딩하는 것이라고 할 수 있겠다.

- pos1 + pos2는 pos1이라는 객체가 +라는 operator+ 멤버함수를 호출할 것인데 매개변수를 pos2로 전달하겠다라는 의미이다.

- 연산자 오버로딩은 두 가지 방법으로 할 수 있는데 위의 방법이 멤버함수로 연산자 오버로딩을 한 것이고, 그 다음 설명할 것이 전역함수로 연산자 오버로딩을 하는 것이다.

 

★ 유의 사항 : 피연산자가 되는 매개변수에 const를 붙여주는 것이 좋다고 한다. 계산된 결과값을 반환하는 게 목적이지, 피연산자의 값을 변경하는 게 목적이 아니기 때문이다.

 

2. 전역 함수 연산자 오버로딩

class Point
{
private:
	int xPos;
	int yPos;
public:
	Point(int x = 0, int y = 0)
		: xPos(x), yPos(y)
	{ }

	friend Point operator+(const Point& pos1, const Point& pos2);
};

Point operator+(const Point& pos1, const Point& pos2)
{
	return Point(pos1.xPos + pos2.xPos, pos1.yPos + pos2.yPos);
}

Point pos1(3, 4);
Point pos2(4, 5);
Point pos3 = operator+(pos1, pos2);
Point pos3 = pos1 + pos2;

- 멤버 함수로 연산자 오버로딩을 배웠다면 다음은 전역함수로의 연산자 오버로딩이다.

- 전역함수로의 연산자 오버로딩은 클래스 내에 friend 키워드를 붙여 연산자 오버로딩을 해야한다. 이때에 매개변수는 따로 호출하는 객체가 없으니 두 개가 될 것이다.

- friend 키워드는 다음에 정리하겠다. 간단히 설명하자면 friend로 선언된 멤버는 그 클래스의 private든 protected든 어디든 접근할 수 있다는 것이다. 그래서 Point 클래스에 속하는 멤버함수가 아닌 전역함수로 선언된 연산자 오버로딩이어도 friend 키워드로 Point 클래스를 참조할 수 있기 때문에 멤버 변수를 자유로이 쓸 수 있다.

- 맨 아래 두 줄이 사용하는 모습인데 결과는 멤버 함수 선언과 다를 것이 하나 없다.

- 특별한 경우 아니면 멤버함수로 선언하는 것이 낫다고 한다.

 

3. 오버로딩이 불가능한 연산자의 종류

- 오버로딩이 불가능한 연산자는 다음과 같다.

. 멤버 접근 연산자
.* 멤버 포인터 연산자
:: 범위 지정 연산자
?: 3항 조건 연산자
sizeof 바이트 단위 크기 계산
typeid RTTI 관련 연산자
static_cast 형 변환 연산자
dynamic_cast
const_cast
reinterpret_cast

- 위의 연산자들이 오버로딩 제한이 된 이유는 C++의 문법규칙을 보존하기 위해서다. 위의 연산자들까지 오버로딩을 허용한다면, C++ 문법규칙에 어긋나는 문장 구성이 가능해지고, 이는 혼란스러운 C++ 언어 사용을 만들 것이다.

- 다음은 멤버 함수를 기반으로 하는 오버로딩이 가능하는 연산자들이다. 객체를 대상으로 진행해야 의미가 통하는 연산자들이기에 멤버 함수 기반으로만 가능하다.

= 대입 연산자
() 함수 호출 연산자
[] 배열 접근 연산자(인덱스 연산자)
-> 멤버 접근을 위한 포인터 연산자

4. 연산자 오버로딩의 주의 사항

 4-1. 본래의 의도를 벗어난 형태의 연산자 오버로딩은 좋지 않다.

pos3 = pos1 + pos2; // 위의 Point 클래스의 객체의 연산이다.

- 위 코드를 보고 어떤 것을 기대할 수 있겠는가? 객체들의 무엇을 할지는 정의를 봐야겠지만 무언가를 더하겠다는 + 연산자의 의미는 직관적으로 기대할 수 있을 것이다. 하지만 연산자 오버로딩에 뜬금없이 멤버변수끼리의 곱하기 연산을 하는 것으로 되어있다면 이 코드를 이해하는 입장에서 혼란스럽지 않겠는가? 그렇기에 최대한 그 뜻에 맞는 연산자 오버로딩을 해야한다.

 

4-2. 연산자의 우선순위와 결합성은 바뀌지 않는다.

- 더하기 연산이 곱하기 연산보다 후순위에 있는 것처럼, 기본적으로 연산자들이 지니고 있는 우선순위와 결합성은 잃지 않고 그대로 따른다. 그것도 바뀐다면 많이 혼란스러울 것이다.

 

4-3. 매개변수 디폴트 값 설정이 불가능하다.

- 오버로딩은 이름이 값지만 매개변수의 자료형과 갯수로 다른 함수 선언인 것을 구분하고 호출하는 방법이다. 하지만 매개변수를 보고 판단해야하는데 매개변수를 디폴트 값으로 설정하고 전달하지 않는다면? 무엇을 보고 컴파일러는 판단해야할지 모를 것이다. 그렇기에 허용하지 않는다.

 

4-4. 연산자의 본래의 기능까지 빼앗을 수 없다.

int operator+(int num1, int num2)
{
	return num1 - num2;
}

- 원래 정수형끼리의 더하기 연산은 정의되어있는데 이것에 대한 재정의를 하여서 다른 의미로 사용되도록 할 수 있는가? 이미 정의가 되어 있기 때문에 허용하지 않는다.

 

- 연산자 오버로딩에 대한 내용은 길어질테니 다음 글에서 계속하겠다.