연산자 재정의 개념과 주의사항을 알아보자! 😯

안녕하세요! 두두코딩 널두 🥸 입니다 ✋
오늘은 연산자 재정의에 대해 알아보겠습니다.

🖇 소스코드에 마우스를 올리고 copy 버튼을 누를 경우 더 쉽게 복사할 수 있습니다!

궁금한 점, 보안점 남겨주시면 성실히 답변하겠습니다. 😁
+ 감상평 댓글로 남겨주시면 힘이됩니다. 🙇

Intro.

이번 포스팅에서는 연산자 재정의 개념과 방법 / 주의사항 에 대해 알아보겠습니다.

연산자 재정의 개념과 방법

사용자 정의 타입으로 덧셈 연산을 수행하면 어떻게 될까?

#include <iostream>

class Complex
{
  int re, im;

public:
  Complex(int r = 0, int i = 0) : re(r), im(i) {}

  void print() const
  {
    std::cout << re << ", " << im << std::endl;
  }
};

int main()
{
  int n = 3 + 4; // 덧셈 연산

  Complex c1(1,1);
  Complex c2(2,2);

  // 이 연산이 수행될까??????????/
  Complex c3 = c1 + c2;
}

위 코드를 수행하면 컴파일 에러 발생한다. 그 이유는 Complex c3 = c1 + c2를 컴파일러가 어떻게 해야될지 모르기 때문이다.

보통 primitve 타입으로 a + b를 수행할 경우 덧셈 연산을 수행한다. 하지만, 사용자 정의 타입 (객체)를 활용할 경우 덧셈을 수행할 수 없다. 이 경우 컴파일러는 아래와 같은 함수를 찾아본다. 만약 해당 함수가 있다면, 활용해 덧셈연산을 수행한다.

// 컴파일러가 사용자 정의 타입 덧셈을 수행할 때 찾는 함수
Complex c3 = c1 + c2;

// 위 코드 수행시 아래와 같은 함수를 찾는다
c1.operator+(c2);
operator+(c1,c2);

우리는 위와 같은 함수를 + 즉, 연산자라고 부르고, 사용자 정의 타입에 따라 덧셈 방식이 다르기 때문에 연산자를 재정의해서 사용하도록 한다. 이를 C++에서는 연산자 재정의라고 부른다.

C++에는 다양한 연산자가 존재하는데, 뒷 부분에서 자세하게 다루도록 하고 해당 포스팅에서는 + 연산자 위주로 설명한다.

c1.operator+(c2); 해당 연산자 재정의 같은 경우, 사용자 정의 타입의 함수를 호출하기 때문에 멤버함수 연산자 재정의라고 부른다.

operator+(c1, c2) 해당 연산자 재정의 같은 경우, 일반함수 연산자 재정의라고 부른다.

우선순위 같은 경우 멤버함수 연산자 재정의가 더 높다. (보통 연관성이 높을 수록 우선순위가 높다!)

2가지 재정의 방법에 대해 알아보자.

멤버함수 연산자 재정의

#include <iostream>

class Complex
{
  int re, im;

public:
  Complex(int r = 0, int i = 0) : re(r), im(i) {}

  void print() const
  {
    std::cout << re << ", " << im << std::endl;
  }

  // return 값으로 class를 전달해야 대입이 가능하다.
  Complex operator+(const Complex& c)
  {
    Complex temp( re + c.re, im + c.im );
    return temp;
  }
};

int main()
{
  Complex c1(1,1);
  Complex c2(2,2);

  // 멤버함수 연산자 재정의된 함수를 호출함.
  Complex c3 = c1 + c2;

  c3.print();
}

위와 같이 멤버함수 내 operator+()를 재정의하면 된다. 해당 함수의 인자로는 동일한 타입을 전달받아 처리하도록 한다. (꼭 동일한 타입을 받을 필요는 없다. 뒷 부분에 자세히 나옴.) 이때, 값 변경 자체가 없고 overhead를 줄이기 위해 const &를 통해 전달받는다.

멤버함수 내 operator() 사용하기 때문에 인자는 하나만 전달하면 된다. 내부적으로 this를 활용해 내부 데이터를 알 수 있고, 전달받은 값을 추가로 더하면 되는 것이기 떄문에 값은 오직 하나만 전달하면 된다.

return 값 같은 경우 대입을 추가로 해야되기 때문에 값으로 전달한다.

함수 내부에서는 사용자가 원하는대로 작성하면 되는데, 보통 임시객체 하나 만들어서 더하고 해당 객체를 반환하는 방식으로 사용한다.

일반함수 연산자 재정의

class Complex
{
  int re, im;

public:
  Complex(int r = 0, int i = 0) : re(r), im(i) {}

  void print() const
  {
    std::cout << re << ", " << im << std::endl;
  }
  // getter를 만들거나 friend로 함수를 등록해줘야함.
  // 이거 없으면 외부에서 접근 불가능해서 에러남.
  friend Complex operator+(const Complex& c1,
                           const Complex& c2);
};

Complex operator+(const Complex& c1, const Complex& c2)
{
  Complex temp(c1.re + c2. re, c1.im + c2.im);
  return temp;
}

int main()
{
  Complex c1(1,1);
  Complex c2(2,2);

  // 일반함수 연산자 재정의된 함수를 호출함.
  Complex c3 = c1 + c2;

  c3.print();
}

위와 같이 일반함수 연산자 재정의 같은 경우 외부함수로 등록하면 된다. 멤버함수 재정의와 달리, 인자가 2개 전달되어야한다는 점을 기억하자.

일반함수 연산자 재정의에서 중요한 점은 외부에서 내부 데이터가 접근이 불가능하다는 점이다. Complex 내부의 re, im 데이터에 접근이 불가능하기 때문에 값을 더하기 위해서는 값을 전달받는 getter()를 만들거나 friend 함수로 등록을 해야된다.

보통 friend 함수로 등록을 해서 사용하니, 기억하도록 하자.

2가지 재정의 방법을 알아보았다. 뒷 부분에서 다른 연산자들을 재정의하면서 조금 더 자세히 알아보도록 한다. 다른 연산자들을 알아보기 전 연산자 재정의 주의사항에 대해 먼저 알아보고 넘어가도록 하자.

연산자 재정의 주의사항

몇 가지 주의사항이 있다. 천천히 보도록 하자.

1. 인자가 모두 primitive 타입인 경우 overloading 불가능

int operator+(int a, int b)
{
  return a - b;
}

위와 같이 인자가 모두 primitive 타입일 경우 +을 구현해두고 -으로 변경하는 기묘한 상황이 발생할 수 있다. 법칙으로 정해진 논리를 깰 수 있기 때문에 인자가 하나는 반드시 사용자 정의 타입이어야한다.

Complex operator(const Complex& c, int n)
{
  // 요건 가능하다.
}

2. 모든 연산자가 다 overloading 되는 건 아니다

., *, ?:, sizeof, typeid, static_cast, dynamic_cast, reinterpret_cast, const_cast 는 재정의가 불가능하다.

C++20 부터. operator는 재정의가 가능하다는 점을 알아두자.

3. 멤버함수와 일반함수 재정의를 모두 제공할 경우 “멤버함수가 우선적” 이다.

4. 첫번째 인자가 사용자정의 타입이 아닌 경우 “일반 함수만 가능” 하다

int main()
{
  Complex c1;
  Complex c2 = 1 + c1;
  // 위와 같이 사용할 경우.
  // 1.operator+(c1)은 불가능함.
  // 오직 operator+(int, complex) 형식으로 호출만 가능함.
}

위의 주석에서 적은것과 같이 첫 번째 인자가 primitive 타입일 경우 멤버함수를 만들 수 없기 때문에 *오직 일반함수 연산자 재정의만 호출가능하다

5. 멤버함수만 사용할 수 있는 연산자 재정의가 존재한다

= () [] -> 와 같은 경우 오직 멤버함수 연산자 재정의만 가능하다

6. 새로운 연산자를 만들거나 인자의 개수를 변경하거나 연산자 우선순위를 변경할 수 없다

7. 디폴트 파라메타 사용 불가능하다

Complex operator+(const Complex& c, int n = 10)
{

}

위와 같이 정의 되어있을 경우 c.operator+() 이런 형식은 안됨. 즉, c1 + (생략불가!!) 형태는 사용불가능하다는 점을 기억하자.

Outro.

해당 포스팅은 Ecourse의 C++ Basic 강의를 참고해 작성되었습니다.

강의를 참고하실 분은 여기를 클릭해 확인해주세요!