Operation Overloading

Date:     Updated:

카테고리:

태그:

모두의 코드 씹어먹는 c++ 자료를 보고 정리한 내용입니다.


C언어에서는 기본적으로 정의되어 있는 데이터 타입(int, float 등)에 대해서 기본 연산자들만 사용이 가능했다. 하지만 C++에서는 사용자 정의 데이터 타입에 대해 사용자 정의 연산자 사용이 가능하다.

:: (범위지정), . (멤버지정), .*(멤버 포인터로 멤버지정), ? true:false, sizeof를 제외한 모든 연산자들을 사용할 수 있다.

  • +, -, /, * 와 같은 산술 연산자.
  • +=, -= 와 같은 축약형 연산자
  • >=, ` ==` 와 같은 비교 연산자
  • &&, || 와 같은 논리 연산자
  • , * 와 같은 멤버 선택 연산자 (여기서 * 는 역참조 연산자. 포인터 * p 처럼)
  • ++, -- 증감 연산자
  • [] 배열에서의 첨자 연산자
  • () 타입 변환 연산자

[ (리턴 타입) operator(연산자) (인자)]

Default Assignment Operator

기본 복사 생성자처럼, 기본 대입 연산자 역시 컴파일러가 기본적으로 지원해주는 연산자이다. 다만, 기본 복사 생성자처럼 얕은 복사를 수행하기 때문에 경우에 따라 반드시 오버로딩이 필요하다.

대입 연산자는 불필요한 복사를 막기 위해 자기 자신을 리턴한다.

// 복사 생성자
class a = b;

// 기본 생성자 + 대입 연산자
class a;
a = b;

Basic Operation

[주의] + 와, ` = 를 오버로딩 했다고 해서, +=` 가 오버로딩 되지는 않음

Operation & Constructor

Complex 클래스를 정의했을 때, 다음과 같이 문자열로도 덧셈을 할 수 있도록 만들고 싶은 경우를 살펴보자

y = z + "3+i2";

가장 먼저 떠올릴 수 있는 방법은 Complex operator+(const char* str) const; 연산을 정의하는 것이다.

double Complex::get_number(const char* str, int from, int to) const {
  bool minus = false;
  if (from > to) return 0;

  if (str[from] == '-') minus = true;
  if (str[from] == '-' || str[from] == '+') from++;

  double num = 0.0;
  double decimal = 1.0;

  bool integer_part = true;
  for (int i = from; i <= to; i++) {
    if (isdigit(str[i]) && integer_part) {
      num *= 10.0;
      num += (str[i] - '0');
    } else if (str[i] == '.')
      integer_part = false;
    else if (isdigit(str[i]) && !integer_part) {
      decimal /= 10.0;
      num += ((str[i] - '0') * decimal);
    } else
      break;  // 그 이외의 이상한 문자들이 올 경우
  }

  if (minus) num *= -1.0;

  return num;
}

Complex Complex::operator+(const char* str) const {
  // 입력 받은 문자열을 분석하여 real 부분과 img 부분을 찾아야 한다.
  // 문자열의 꼴은 다음과 같습니다 "[부호](실수부)(부호)i(허수부)"
  // 이 때 맨 앞의 부호는 생략 가능합니다. (생략시 + 라 가정)

  int begin = 0, end = strlen(str);
  double str_img = 0.0, str_real = 0.0;

  // 먼저 가장 기준이 되는 'i' 의 위치를 찾는다.
  int pos_i = -1;
  for (int i = 0; i != end; i++) {
    if (str[i] == 'i') {
      pos_i = i;
      break;
    }
  }

  // 만일 'i' 가 없다면 이 수는 실수 뿐이다.
  if (pos_i == -1) {
    str_real = get_number(str, begin, end - 1);

    Complex temp(str_real, str_img);
    return (*this) + temp;
  }

  // 만일 'i' 가 있다면,  실수부와 허수부를 나누어서 처리하면 된다.
  str_real = get_number(str, begin, pos_i - 1);
  str_img = get_number(str, pos_i + 1, end - 1);

  if (pos_i >= 1 && str[pos_i - 1] == '-') str_img *= -1.0;

  Complex temp(str_real, str_img);
  return (*this) + temp;
}

코드는 잘 동작하겠지만 + 뿐만 아니라 다른 연산들에서도 str을 complex로 바꿔주는 번거로운 작업들이 중복될 것이다. 이러한 번거로운 작업을 막기 위해 Complex(const char* str); 생성자를 추가하는 것도 좋은 방법이다. 그러면 + 연산이 아래와 같이 간단하게 구현될 것이다.

Complex Complex::operator+(const char* str) const {
  Complex temp(str);
  return (*this) + temp;
}

이때 굉장히 신기한 지점은 operator+(const char *str) 함수를 제거해도 아래 코드가 정상적으로 실행된다는 것이다.

y = z + "3+i2";

코드가 어떻게 작동하는지 따라가 보자.

y = z + "3+i2";

우리가 위와 같은 코드를 실행하면 컴파일러는 위 문장을 다음과 같이 해석한다.

a = a.operator+("-1.1 + i3.923");

이때 우리는 operator+(const char *str)가 없기 때문에 직접적으로 오버로딩 되지는 않는다. 하지만 컴파일러는 매우 똑똑하기 때문에 그 다음 순위로 오버로딩 될 수 있는 함수가 없는지 찾아보게 된다. 이때 우리가 정의한 생성자 Complex(const char* str)가 있기 때문에 위 문장은 다음과 같이 변환된다.

a = a.operator+(Complex("-1.1 + i3.923"));

[하지만 이러한 방법에도 한계가 존재한다.]

a = a + "-1.1 + i3.923"; // OK

a = "-1.1 + i3.923" + a; // Error!

Friend

클래스 내부에서 다른 클래스나 함수들을 friend로 정의할 수 있는데, friend로 정의된 클래스나 함수들은 원래 클래스의 private으로 정의된 변수나 함수에 접근이 가능하다.

한 가지 특이한 점은 일방적인 관계라는 것이다.

Binary Operation

임의의 연산자 @에 대해서, 컴파일러가 a@b를 처리하는 방식은

*a.operator@(b);
*operator@(a, b);

둘 중 가능한 녀석을 택해 처리하게 된다.

  • *a.operator@(b) : a 클래스의 멤버 함수
  • *operator@(a, b) : 클래스 외부에 정의되어 있는 일반적인 전역함수

    이항 연산이 아닌 단항 연산자들은(ex. [ ] ) 멤버함수로만 존재가 가능하고, 전역함수로 뺄 수 없다.

따라서 두 버전이 모두 존재할 경우 어느 버전을 실행할지 모르기 때문에, 다음 문장은 컴파일 에러가 발생한다.

a = a + a;

일반적으로 한 객체의 값이 바뀐다던가, 자기 자신을 리턴하지 않는 이항 연산자는 전역함수로 선언하는 것이 원칙이다.

IO Operator

std::cout << a;

라는 것은 사실 std::cout.operator<<(a) 를 하는 것과 동일한 명령이다. 즉, std::cout이라는 객체에 멤버한수 operator<<가 정의되어 있어서 a를 호출하는 것이다. 우리가 std::cout을 자유롭게 사용할 수 있었던 이유는 아래와 같이 수많은 함수들이 오버로딩 되어 있기 때문이다.

ostream& operator<<(bool val);
ostream& operator<<(short val);
ostream& operator<<(unsigned short val);
ostream& operator<<(int val);
ostream& operator<<(unsigned int val);
ostream& operator<<(long val);
ostream& operator<<(unsigned long val);
ostream& operator<<(float val);
ostream& operator<<(double val);
ostream& operator<<(long double val);
ostream& operator<<(void* val);

이때 우리가 정의한 Compex 클래스에 « 연산자를 사용하고 싶으면 어떻게 해야할까? 당연하게도, 표준 헤더파일 내에 존재하는 ostream 클래스에 operator« 함수를 추가하는 것은 불가능하다. 따라서 ostream 클래스 객체와 Complex 객체를 인자로 받는 operator« 함수를 정의해야 한다.

여기서 문제는 operator << 에서 Complex의 private 멤버 변수에 접근할 수 없다는 것이다. 이를 해결하기 위한 가장 괜찮은 방법은 Complex 클래스 내부에 operator <<friend로 지정하는 것이다.

friend ostream& operator<<(ostream& os, const Complex& c);

Other Operators

[첨자 연산자] []

char& MyString::operator[](const int index) { return string_content[index]; }

[타입 변환 연산자] ()

operator int() { return data; }
operator viod*() { return; }
  • 리턴 타입 명시 X
  • 타입 변환 연산자 정의해 놓으면 컴파일러가 알아서 찾아다 씀
[증감 연산자] ++, --
파라미터 존재 여부로 구분

전위 증감 연산자

operator++();
operator--();

A& operator++() {
  // A ++ 을 수행한다.
  return *this;
}
  • 함수 인자 X
  • 값이 바뀐 자기 자신을 리턴

후위 증감 연산자

operator++(int);
operator--(int);

A operator++(int) {
  A temp(A);
  // A++ 을 수행한다.
  return temp;
}
  • 함수 인자 O (역할x)
  • 값이 바뀌기 이전의 객체를 리턴
  • 임시 객체가 들어가기 때문에 조금 더 느림



맨 위로 이동하기

Cpp 카테고리 내 다른 글 보러가기

댓글 남기기