본문 바로가기
프로그래밍/C++

연산자 오버로딩(operator overloading)(1)

by 리뷰하는 (게임)프로그래머_리프TV 2010. 4. 4.




기존에 존재하는 +, ++, == 등의 연산자를 프로그래머가 중복 정의가 가능하다.

operator+, operator++ 등으로 오버로딩이 가능한데,

멤버 함수내에 선언할 수도 있고, friend 키워드를 사용해서 전역으로 사용 할 수도 있지만,

멤버 함수에 선언해서 사용하는 것에 대해서 먼저 살펴 보자.

연산자 오버로딩의 사용 예,

#include <iostream>

class Point
{
	int x, y;
public:
	Point( int _x = 0, int _y = 0 ) : x(_x), y(_y) {}
	void ShowPoint(){std::cout << x << " " << y << std::endl;}
	int Get_x() const{return x;}
	int Get_y() const{return y;}
	// Point Class x, y에 동시에 값 증가.
	void operator+(int val);
	// 인스턴스 A - 인스턴스 B
	Point operator -(const Point& p) const;
	// x,y값을 1 증가
	Point& operator++();
	// x,y값을 1 감소
	Point& operator--();
};

Point& Point::operator++()
{
	x++;
	y++;
	return *this;
}

Point& Point::operator--()
{
	x--;
	y--;
	return *this;
}

Point operator +(const Point& p1, const Point& p2)
{
	Point tmp(p1.Get_x()+p2.Get_x(), p1.Get_y()+p2.Get_y());
	return tmp;
}

Point Point::operator -(const Point& p ) const
{
	Point tmp(x-p.x, y-p.y);
	return tmp;
}

void Point::operator +( int val)
{
	x+=val;
	y+=val;
}

void main()
{
	Point p1(1, 2);
	Point p2(3, 3);
	p1.ShowPoint();
	// p1.operator++(); 과 같다.
	p1++;	// operator ++ 의 사용
	p1.ShowPoint();
	// p1.operator++(); 과 같다.
	++p1;	// operator ++ 의 사용
	p1.ShowPoint();
	// p1.operator+(10); 과 같다.
	p1+10;
	p1.ShowPoint();
	// p1 = p1.operator-(p2); 와 같다. 
	p1=p1-p2;
	p1.ShowPoint();
	// p1 = p1.operator+(p2); 와 같다. 
	p1=p1+p2;
	p1.ShowPoint();
}
// 결과
// 1 2
// 2 3
// 3 4
// 13 14
// 10 11
// 13 14​



operator++을 보면 인자를 받지 않고 return *this를 해주고 있다.
설명이 필요 없을지도 모르겠지만, 멤버 함수 내에 선언되어 있는 존재이기 때문에 자신의x,y를 증가 시키고
*this를 넘겨 줌으로써 값을 받아 온다.
만약 *this를 쓰지 않고 return tmp; 같은 식으로 하게 된다면,
++(++p);의 경우,
++p(1증가);
다시 1 증가
총 2증가를 하게 되지만,

복사본을 사용한다면,
++(++p);의 경우,
++p(p의 복사본,1증가);
p의 값은 1 증가,
p의 복사본이 1증가,
결론은 1밖에 증가가 되지 않는다.
다음은 전역에 선언한 operator에 대해서 살펴 보자.

#include <iostream>

class Point
{
	int x, y;
public:
	Point( int _x=0, int _y=0 ) : x(_x), y(_y){}
	// 외부에서 내부 데이터를 건들기 때문에
	// friend 선언을 해주어야 하는 단점이 존재.
	// 정보 은닉에 대한 문제가 생길 수 있다.
	friend std::ostream& operator << (std::ostream& os, const Point& p);
};

// 문자열을 저장하는 ostream!
std::ostream& operator << (std::ostream& os, const Point& p)
{
	os << "[" << p.x << ", " << p.y << "]" << std::endl;
	// 원하는 식으로 출력 형태를 정하고
	// os를 return 해 주고 있다.
	return os;
}

void main()
{
	Point p(1,3);
	// p를 출력 하고 있다.
	std::cout << p;
}
// 결과
// [1, 3]​


이 처럼 사용이 가능하지만,
가능하면 객체지향적인 코딩을 생각해서 멤버 변수에 연산자 오버로딩을 하는 것이 옳은 방법이라고 생각한다.


그리고 ++p와 p++에 대해서 차이점이 존재한다.
실제로는 선연산 , 후연산이지만 operator를 사용해서는 그 의미가 불문명 해지게 된다.
이럴 경우를 대비해
Point& operator++(); ++p와 같고,
Point& operator++(int); p++과 같다.
예제를 잠깐 보자.

// 차이가 있는 부분만 확인.
Point Point::operator++(int)
{
	std::cout << "p++" << std::endl;
	Point temp(x, y);
	x++;
	y++;
	return temp;
}

Point& Point::operator++()
{
	std::cout << "++p" << std::endl;
	x++;
	y++;
	return *this;
}
void main()
{
	Point p1(1, 2);
	(p1++).ShowPoint();	// 1, 2 출력
	p1.ShowPoint(); // 2, 3 출력

	Point p2(1, 2);
	(++p2).ShowPoint();	// 2, 3 출력
	p2.ShowPoint(); // 2, 3 출력
}
// 결과
// p++ 
// 1 2
// 2 3
// ++p
// 2 3 
// 2 3 ​


하지만 이 방법에는 큰 문제점이 존재하는데,
위에서 언급 하였던
(++(++p))와
((p++)++)우리가 예상 했던 결과가 나오지 않는 다는 것이다.

void main()
{
	Point p1(1, 2);
	// 후연산
	// 복사로 인한 연산이기 때문에 중복이 아니된다.
	// Point Point::operator++(int)
	((p1++)++).ShowPoint();	
	p1.ShowPoint();

	Point p2(1, 2);
	// 선연산 this를 사용하기 때문에 중복이 가능하다.
	// Point& Point::operator++()
	(++(++p2)).ShowPoint();	
	p2.ShowPoint();
}
// 결과 
// p++
// p++
// 1 2
// 2 3
// ++p
// ++p
// 3 4
// 3 4​


연산자 오버로딩이 안되는 경우도 존재 하는데,
. .* :: ?: sizeof 는 할 수 없다고 한다.
마지막으로 주의점에 대해서 이야기 하겠다.
1. p+3;
이 부분은 p의 값들에 3을 더할 수도 있겠고,
또는 p에 3을 더해서 새로운 객체를 생성해서 리턴하는 것일 수도 있다.
즉 저 부분만 봐서는 어떤 일을 하는지 알 수 없다는 것이다. (직접 operator+함수를 보기 전까진.)

2. p+3*3;
연산자는 계산시에 우선 순위가 존재 한다.
만약 저렇게 연산자 오버로딩을 하였다고 하더라도, 순서는 *3 이후 +3을 하게 된다는 것이다.

3. void operator+(int val=10);
이 처럼 연산자 오버로딩은 디폴트 매개 변수를 가질 수 없다. 만약 가능하다면,
p+; 이런 구문이 나올지도 모르고 이는 분명 프로그래머가 원하는 방향은 아닐 것이다.

4. int operator+(int a, int b){ return a+b+3 }
이것 또한 안된다. 연산자 오버로딩을 하지 않은 (int)3+(int)4라는 기본 연산에 대해서 다시 오버로딩을 하는 것은 불가능하다.

마지막으로 전역으로 선언된 연산자 오버로딩과, 멤버 함수로 선언된 연산자 오버로딩은 조금의 불러오는 차이가 존재 하는데,

++p -> 멤버 함수의 경우( p.operator++(); ) 와 같고,
++p -> 전역 함수의 경우( operator++(p); ) 와 같다는 걸 기억해 두자.

'프로그래밍 > C++' 카테고리의 다른 글

임시 객체 생성  (0) 2010.04.04
연산자 오버로딩(operator overlonding)(2)  (0) 2010.04.04
가상 복사 생성자  (0) 2010.04.03
virtual(가상)  (0) 2010.04.03
함수 오버라이딩(overriding)  (0) 2010.04.03