이 부분을 어떻게 설명해야할지 매우 어렵다. 음… 그래서 이해한대로 기록해보려고 한다!
어떻게 연산자를 멤버 함수 또는 비멤버 함수로 선언하여 오버로딩 할 수 있을까? 먼저 연산자를 멤버 함수로서 사용하는 구문을 간단한 식을 통해 알아보자. 1 + 1
을 표기하는 방법은 여러 가지가 있다.
+ 1 1 // prefix
1 + 1 // infix
1 1 + // postfix
infix
표기법은 우리가 수학을 할 때 자주 봤던 표기법이라 익숙하다.
prefix
에 괄호를 추가해보자. +( 1, 1 )
이렇게 보면 함수 표기법이 된다.
postfix
표기법은 스택 응용을 할 때 활용한다. 어떻게 활용한다는 이야기인지 아직 잘 모르겠다.
그 중 1 + 1
을 이렇게 표기하면 객체, 멤버 함수, 매개변수처럼 표기할 수 있다.
1.+( 1 )
앞에 1
은 객체, +
는 멤버 함수, 괄호 안에 1
은 매개 변수가 된다.
(객체는 this 를 통해 자신을 가리킬 수 있으므로 매개 변수에 따로 넣어 주지 않아도 된다.
`1.+(1 + 1)` 이지만 풀어쓰면 `1.+(this, 1)` 이고, `this`는 생략하기 때문에 없어도 된다는 것이다.)
잘못된 지식일 수 있다... ( 과감한 지적 환영한다 )
예시를 통해 다음을 이해하자.
먼저 헤더 파일에서 연산자 오버로딩 식을 보자.
#ifndef INTEGER_CLASS_HPP
# define INTEGER_CLASS_HPP
# include <iostream>
class Integer
{
private:
int _n;
public:
Integer(int const n);
~ Integer(void);
int getValue(void) const;
[=연산자] Integer& operator=(Integer const& rhs);
[+연산자] Integer operator+(Integer const& rhs) const;
};
[<<연산자]std::ostream& operator<<(std::ostream& o, Integer const& rhs);
#endif
오버로딩한 코드 줄 맨 앞에 표시를 했다. 위에서는 =
, +
, <<
연산자에 오버로딩을 했다.
신기한 것을 볼 수 있다.
❗️이 부분은 잘못된 지식이 있을 수 있다.
=
과 <<
연산자 오버로딩의 반환형은 참조인데, +
연산자 오버로딩의 반환형은 값이다.+
연산자는 매개 변수로 값을 받아온다. 객체나 배열을 다루지 않고 값을 연산하기 때문에 객체를 생성해서 연산된 값을 반환해야한다.=
는 반환값이 자기 자신이다. 그렇기 때문에 함수 내부에서 this 포인터로 자신을 반환한다. 예를 들어 a = b
는 다음과 같이 표현될 수 있다. a.operator=(b)
, 이때 a
자신을 반환해야하기 때문에 *this
를 반환한다.<<
연산자를 클래스 밖에서 오버로딩 하는 이유는 추측해보자면, =
, +
처럼 Integer 클래스의 객체가 아닌, std
네임 스페이스 안의 ostream
객체를 사용하기 때문에 Integer 클래스 내부에 오버로딩할 필요가 없다.생성자와 소멸자, +
, =
, <<
가 실행되면 각각 announce 를 해준다.
#include <iostream>
#include "Integer.class.hpp"
Integer::Integer( int const n ) : _n( n ) {
std::cout << "Constructor called with valut " << n << std::endl;
return;
}
Integer::~Integer( void ) {
std::cout << "Destructor called with value " << this->_n << std::endl;
return;
}
int Integer::getValue(void) const { return this->_n; }
Integer& Integer::operator=( Integer const & rhs ) {
std::cout << "Assignation operator called from " << this->_n;
std::cout << " to " << rhs.getValue() << std::endl;
this->_n = rhs.getValue();
return *this;
}
Integer Integer::operator+( Integer const & rhs ) const {
std::cout << "Addition operator called with" << this->_n;
std::cout << " and " << rhs.getValue() << std::endl;
return Integer(this->_n + rhs.getValue());
}
std::ostream & operator<<(std::ostream & o, Integer const & rhs) {
std::cout << "<< operation overload and 적용" << std::endl;
o << rhs.getValue();
return o;
}
결과 값과 함께 연산자 오버로딩과 객체의 생성 및 소멸 과정을 보자.
#include <iostream>
#include "Integer.class.hpp"
int main(void) {
Integer x(30);
Integer y(10);
Integer z(0);
[1.<< 연산자] std::cout << "Value of x : " << x << std::endl;
[2.<< 연산자] std::cout << "Value of y : " << y << std::endl;
// 같은 메모리 주소를 가진 복사 생성자를 생성해서 값을 바꾼 후에 소멸
[3.= 연산자] y = Integer(12);
[4.<< 연산자] std::cout << "Value of y : " << y << std::endl;
[5.<< 연산자] std::cout << "Value of z : " << z << std::endl;
[6.=,+ 연산자] z = x + y;
[7.<< 연산자] std::cout << "Value of z : " << z << std::endl;
return 0;
}
main 함수에서 Integer 클래스로 x, y, z 객체를 생성했다. 각각 _n 변수에 30, 10, 0 을 대입했다.
터미널 1 ~ 3줄을 보면 알 수 있다.
1 ~ 2.<< 연산자
를 활용한 << x
와 << y
를 보자.
4, 5 번째 줄을 보면, 오버로딩 <<
를 사용하는 것을 확인할 수 있다.
3.= 연산자
를 활용한 y = Integer(12)
를 보자.
6 번째 줄을 보면, 생성자를 통해 12 를 대입한 객체가 생성된 걸 확인할 수 있다.
그 후 오버로딩 =
를 사용한다는 걸 7 번째 줄을 보면 알 수 있다. 10 대신 12를 대입한다.
대입 후 오버로딩 =
연산이 종료되면서 앞서 생성했던 객체가 필요 없어지며 소멸된다.
4 ~ 5.<<연산자
는 1 ~ 2.<<연산자
와 같다.
6.=, + 연산자
를 보자.
터미널 11 ~ 14를 보면 + 연산자
를 먼저 실행하고 = 연산자
를 실행한다.
+ 연산자
는 새로운 객체를 생성하여 반환하기 때문에 12번째 줄에 생성자가 호출된다.=연산자
에서 0 대신 42를 대입한 후 생성했던 객체가 필요 없어지며 소멸(14줄)된다.7.<<연산자
는 이전과 똑같다. 터미널 15번째 줄이다.
모든 과정이 끝나고 소멸자가 생성되며 프로그램이 종료된다.
음... 아직 많이 어렵다. 아마 잘못된 지식이 많이 있을텐데, 혹시나 이 자료를 보게 된다면 부디 잘 정리 되어있는 다른 자료도 참고하시길 바란다. 그리고 나도 더 열심히 하자!