본문 바로가기

예전/C, C++

[C++] 연산자 오버로딩


연산자 오버로딩



• 연산자란?

 연산자란 연산을 하기 위해 필요한 것으로 C++에는 많은 연산자가 있다.

연산자에는 기본적인 사칙연산 ( +, - , * , / , % ) 이나 대입연산( = ), 관계연산, 논리연산자가 있다.

연산자 오버로딩을 통해 이 많은 연산자들을 오버로딩 할 수 있다.


• 오버로딩이란?

 오버로딩은 함수의 이름이 같고, 시그니쳐의 타입 혹은 개수에 따라 함수가 구분되는 것을 말한다. 여기서 리턴 값, 즉 반환형은 중요하지 않다.


그렇다면 연산자 오버로딩이란 무엇일까?



• 연산자 오버로딩이란?

 연산자 오버로딩이란 C++에서 연산자가 하는 일을 함수로 개인의 필요에 맞게 구현한 것이다. 예를 들어, 클래스의 성격에 따라 필요한 연산 기능이 있다면 그에 맞게 동작하도록 기본 연산자의 기능을 재정의할 필요가 있는데 이것을 연산자 오버로딩이라 한다.

 오버로딩한 함수는 operator<연산자>형태의 이름을 가지고 있다이항 연산자의 경우 좌측의 피연산자는 호출할 객체가 되며, 우측의 피 연산자는 인수가 된다.


연산자 오버로딩의 방법은 크게 2가지로 나뉠 수 있다. 


 첫 번째 오버로딩은 클래스 멤버함수에서 클래스 객체를 활용하기 위한 오버로딩 즉, 멤버 함수에 의한 오버로딩이고, 두 번째는 일반 함수에 의한 오버로딩이다.



예를 들어, 복소수를 의미하는 Complex라는 클래스를 만들었다고 하자.


두 복소수의 덧셈을 위해 1과 같이 Add라는 함수를 만들어 쓸 수 있다지만 산자 오버로딩을 하면 2와 같이 + 연산자를 통해 복소수의 덧셈을 할 수 있게 된다.

이는 1보다 이해하기 쉬운 코드를 만들어 내며기본 타입과 사용자 정의 타입을 같은 방식으로 다룰 수 있기 때문에 매우 편리하다.




• 멤버 함수에 의한 오버로딩



















 


Output() 결과를 보면

c3 = c1 + c2;

c3 = c1.operator+(c2); 같은 결과가 나왔다.








 이것이 바로 연산자 오버로딩이다


연산자 오버로딩 객체를 연산자로 하게되면 해당 연산자 operator 함수가 호출되는 일종의 약속이다


연산자는 operator+처럼 operator함수 뒤에 붙게 되며 여러 타입의 매개 변수를 받아 처리할 수 있게 만들어질 수 있다.



[ 잠깐 ]  임시 객체에 대한 이야기


 앞에서 realimag 변수를 만들고 , 이를 Complex라는 임시객체에 담아 return 했다. return 한 후 임시객체는 소멸한다. 임시 객체를 사용할 경우, 최적화가 진행되 속도가 빨라진다.

임시객체를 사용하는 것이 때로 효율적이기 때문에 이를 사용한다.




• 일반 함수에 의한 오버로딩


 


 이 일반함수는 클래스의 멤버 함수가 아니기 때문에 멤버변수에 접근하는 것이 불가능하다. 따라서 friend를 써줘야 한다.









 friend가 의미하는 것은 위의 함수를 Complex 클래스의 친구로 만들겠다는 뜻이다. 친구로 지정하면 Complex 클래스의 모든 멤버에 접근이 가능해 진다.


그림으로 정리해보자.



 두 가지 모두 연산이 가능한 형태라면 멤버함수에 의한 오버로딩이 더 좋다. 왜냐하면 프로그램이 간결해지기 때문이다.





연산자 오버로딩 주의사항


• 기존 연산 방법을 바꿀 수 없다.

 -  연산자 오버로딩을 할 때 피 연산자들 중에서 적어도 하나는 객체가 되어야 한다.

int operator+(int left,int right);

만약 위와 같은 함수를 만든다면 이는 정수의 덧셈방법을 변경할 수 있는 것이므로 혼란을 초래할 수 있다.

 -  연산자의 우선 순위나 계산 순서를 변경하는 것도 불가능하다.


• 디폴트 매개 변수 설정이 불가능하다.

• 기존 연산자의 기능 해치지 말자.
- 예를 들어 +연산자를 연산자 기능으로 만드는 것은 아주 말도 안 되는 짓이다. 왜냐하면 연산자 오버로딩의 목적은 클래스의 편리한 사용과 이해하기 쉬운 코드에 있기 때문이다.




기타 연산자 오버로딩



• 피 연산자 오버로딩



 피 연산자가 한 개인 연산자, 예를 들어 ++, -- 연산자인 경우엔 피 연산자의 앞 뒤에 모두 올 수 있고, 이는 각각 다른 의미를 가지기 때문에 구분해 알아두어야한다.


 이 코드에서 주의깊게 볼 것은 전치와 후치 연산자를 별개로 만들었다는 것, 그리고 후치 연산임을 알리기위해 int타입의 연산자를 하나 두었다는 것이다.


 보통 피연산자가 하나인 연산자를 오버로딩 할때는 시그니쳐가 없는 원형을 사용한다. 에서의 전치 연산자처럼 말이다. 하지만 ++, --후치연산만 이 규칙에 대해 예외가 된다.



• iostream 객체 오버로딩


 


 연산자 오버로딩을 할 때 오른쪽 피 연산자가 객체인 경우 반드시 일반 함수를 사용해야 한다.

coutostream 클래스의 객체, cinistream 클래스의 객체이다. 둘다 std namespace 내의 클래스 이다.

여기서 return 형으로 ostream을 해주고 있다. 다시 cout 의 객체를 반환해야 할까?

이는 << 연산자의 사용법과 관련있다. 연산자 우선순위 표에 따르면 << 연산자를 동시에 사용할 경우 좌측의 연산부터 수행한다. 그렇기 때문에

cout << c1 을 먼저 수행하고, 그 결과 값인 coutcout << “\n” 을 수행하게 된다.

우리가 만든 operator함수는 cout객체를 반환하기 때문에 결국 cout << “\n” 연산을 수행하게 된다. 결론적으로 << 연산자를 여러 개 붙여서 사용할 수 있도록 만들기 위해서 cout 객체를 반환한다.




• 배열 인덱스 연산자 오버로딩



[]operator로 오버로딩이 가능하다.



• 대입 연산자 오버로딩


 복사 생성자는 객체 간 대입연산을 할 경우 호출된다. 단 얕은 복사를 한다.

연산자 내에서 동적할당을 한다면 깊은 복사를 해야하고 , 이는 직접 정의해야한다.

대입 연산자를 정의해놓지 않으면 내부적으로 operator= 함수가 컴파일러에 의해 내부적으로

생성되는데 이를 디폴트 대입 연산자라고 한다. 생성자와 소멸자는 클래스 내에서 정의하지 않으면 내부적으로 생성된다. 이때 대입연산자를 정의한 operator= 함수도 생성되는데 내부적으로 생성되는 생성자, 소멸자는 아무 명령도 수행되지 않지만 복사 생성자와 대입연산자는 좌측 객체에 우측 객체가 비트 단위로 복사된다. 이 때문에 같은 타입의 객체의 대입 연산이 가능한 것이다.



디폴트 대입 연산자의 문제는 디폴트 복사 생성자의 문제와 같다. 그것은 문자열을 복사했을시 깊은 복사가 되지 않고, 메모리의 누수가 일어난다는 점이다. 이는 디폴트 복사 생성자의 해결 방법으로 처리할 수 있다.




• const 함수를 이용한 오버로딩의 활용



const 유무도 함수 오버로딩의 조건이 된다.


const 참조자로 참조하는 경우의 함수 호출을 위해서 정의된 함수이다. 이 함수를 대상으로는 멤버 변수의 값을 변경할 수 없으니, const로 선언되지 않은 다른 함수가 추가되어야 한다.


일반적으로 operator[] 함수는 const 함수와 일반 함수가 동시에 선언된다.


const 객체는 멤버변수의 변경이 불가능한 객체이다. const 객체는 const 참조자로만 참조가 가능하다. const 객체의 대상으로는 const 함수만 호출가능하다.

반환형이 const 란 의미는 반환되는 객체를 const 화 시키겠다는 의미이다. 따라서 반환되는 객체를 대상으로 const 로 선언되지 않은 함수의 호출이 불가능하다.