++ 전위형 / 후위형 연산자 재정의를 구현해보자

안녕하세요! 두두코딩 널두 🥸 입니다 ✋
오늘은 ++operator를 구현해보겠습니다.

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

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

Intro.

이번 포스팅에서는 전위형 / 후위형 ++연산자를 만들면서 재미난 기법들을 배워보겠습니다.

++ 연산자 재정의

++ 연산자를 재정의해 사용할 수 있다. 해당 기법을 만들면서 다양한 테크닉을 배워볼 수 있는데, 만들면서 익혀보자.

#include <iostream>

int main()
{
  int n1 = 3;

  ++n1;

  int n2; // 이렇게 만들면 쓰레기 값을 가지게 된다.
          // 보통 C 계열언어에서는 값으로 초기화하지 않고
          // 생성을 할 경우 쓰레기 값을 담는다.

  // 그렇다면 초기화 할 때 값을 0으로 지정해주는
  // Int형 객체를 만들어 사용하는 건 어떨까?

  Integer n3;
}

위의 코드의 주석과 같이, int형 같은 경우 초기화 하지 않으면 쓰레기 값이 담긴다. 쓰레기 값을 갖고 어떤 동작을 처리하다보면 버그에 직면하게 되는데, 이를 막고자 Integer 객체를 만들어 사용하고자 한다.

Integer 객체 같은 경우, 생성자를 활용해 기본 값을 설정할 수 있다. 아래의 코드는 Integer 객체를 만든 코드이다.

class Integer
{
  int value;
private:
  Intger(int n = 0) : value(n) {}

  void print() const
  {
    std::cout << value << std::endl;
  }
};

int main()
{
  Integer n1 = 3;

  n1.print(); // 3 출력

  ++n1;
}

위의 코드와 같이 생성자를 활용해 값을 초기화 할 수 있다.

우리가 보통 int를 사용할 때, ++연산자를 많이 사용하는데 ++ 연산자를 Integer에 맞게 재정의해서 사용해보자.

전위형 ++ 연산자

int main()
{
  Integer n1 = 3;

  // 아래의 동작이 가능해야된다.
  // 즉, Integer와 동일한 타입을 리턴해야된다.
  Integer n2 = ++n1;
}

위의 추론을 통해 Interger 타입과 동일한 값으로 리턴해야된다는 것을 알게되었다. 이를 기반으로 ++ 연산자를 재정의해보자.

class Integer
{
  int value;
private:
  Intger(int n = 0) : value(n) {}

  void print() const
  {
    std::cout << value << std::endl;
  }

  Integer operator++()
  {
    // 여기서 처리하고.
    value++; // 값 증가

    // 동일한 타입을 반환하면 됨.
    return *this;
  }
};

위와 같이 operator++() 재정의에서는 2단계로 나눠 생각하면 된다.

1. 값을 증가시킴 2. 동일한 타입을 반환하면 됨

여기서 한단계 더 나아가서 생각해보자. 아래의 코드가 잘 동작할까?

int main() {
  Integer n1 = 3;
  ++++n1;

  // 값이 뭐가 나올까?
  n1.print();
}

위 코드를 동작하면 잘 동작한다. n1.print()를 수행했을 때, 어떤 값이 나올까? 5가 나올 것 같지만, 4가 출력된다.

그 이유는 operator++()의 리턴 값이 임시객체 이기 떄문이다. 우리가 앞선 포스팅에서 주구장창이야기 했던 값 반환은 임시객체이다. 따라서, 연쇄적 동작을 위해 참조반환을 해야된다고 이야기했다.

즉, 위의 코드는 좀 풀어서 생각해보자면, 아래와 같다.

int main() {
  Integer n1 = 3;

  // 아래의 코드 풀어보면..
  // (n1.operator++()).operator++()
  ++++n1;

  // 값이 뭐가 나올까?
  n1.print();
}

위 코드 주석에 나와있는 것과 같이, ++++n1을 하게될 경우 (n1.operator++()).operator++()와 같은 형식이 된다. (n1.operator++()) 의 결과 값 즉, 임시객체의 operator++()을 호출하는 것이기 떄문에 결과적으로 n1에서는 오직 한번의 ++연산만 호출 된다.

따라서, 값은 4가 출력될 것이다.

이를 막기위해서는 참조 값 반환이 절실하다.

class Integer
{
  int value;
private:
  Intger(int n = 0) : value(n) {}

  void print() const
  {
    std::cout << value << std::endl;
  }

  // 코드 수정해보자!
  Integer& operator++()
  {
    // 여기서 처리하고.
    value++; // 값 증가

    // 동일한 타입을 반환하면 됨.
    return *this;
  }
};

위의 코드와 같이 &를 반환할 수 있도록 수정해보자! 그럼 결과적으로 5를 출력하는 것을 볼 수 있을 것이다.

후위형 ++연산자

자! 여기서 생각해볼게 있다. 우리가 ++연산자라고 하면 전위형 방식과 같이 ++n 앞에 연산자를 적는 방법도 있지만 후위형 방식과 같이 n++ 연산자를 뒤에 적는 방법도 있다.

근데 우리가 연산자를 재정의 하기 위해서는 operator++() 라는 이름으로 재정의를 하는데, 이게 전위형 연산자인지 후위형 연산자인지 구별할 방법이 없다. C++ 표준에서는 이를 구별하기 위해서 후위형 ++ 연산자 일경우 임시의 int 타입을 인자로 넣어 주어라고 명시해뒀다.

즉, 사용하지는 않지만 int 타입의 인자를 적어주면 사용자정의 타입 ++ 후위형 연산자를 호출할 때 해당 함수가 불리도록 되어져있다.

class Integer
{
  int value;
private:
  Intger(int n = 0) : value(n) {}

  void print() const
  {
    std::cout << value << std::endl;
  }

  // int를 적으면 후위형으로 인식함!!!!!!!!!!!!!
  Integer operator++(int)
  {
    // 기존 값을 반환해야되기 때문에
    // 먼저 기존 값을 저장
    Integer temp = *this;

    ++value;
    return temp;
  }
};

위 코드와 같이 작성하면 후위형 연산자를 호출한다. 해당 코드에서 관심을 가지고 볼 부분은 2가지이다.

1. 기존 값을 저장해둔다.

2. 값 리턴을 한다

더 나아가 함수 내 디자인을 조금 수정해보자.

우리가 만약 ++ 연산자를 1씩 무조건 증가시키는 것이 아니라, 홀수 일 경우 1씩 증가시키고, 짝수일 경우 2씩 증가시키는 정책을 도입했다고 가정해보자. 이떄 우리는 전위형 / 후위형 연산자를 둘 다 수정해야된다.

함수의 정책 변화로 기존에 잘 돌아가는 코드를 많이 변경하는것은 또 다른 버그를 유발할 수 있다. 변하는 부분은 최소화하는게 좋은데, 디자인을 조금 수정해 더 나은 코드로 만들어보자.

class Integer
{
  int value;
private:
  Intger(int n = 0) : value(n) {}

  void print() const
  {
    std::cout << value << std::endl;
  }

  Integer& operator++()
  {
    ++value;
    return *this;
  }

  // int를 적으면 후위형으로 인식함!!!!!!!!!!!!!
  Integer operator++(int)
  {
    // 기존 값을 반환해야되기 때문에
    // 먼저 기존 값을 저장
    Integer temp = *this;

    // 직접 변경하는것이 아니라 전위형을 통해 변경
    // ++value;
    ++(*this);
    return temp;
  }
};

위의 코드와 같이 후위형 연산자 내에서 전위형 연산자를 호출해 실질적 값이 변경되는 부분을 전위형 연산자에게 위임하는 방법이다. 생각해보면 어짜피 operator++(int) 와 같은 후위형 연산자 같은 경우 임시로 값을 저장하고 저장된 값을 반환하면 되는 것이고, 실질적 값은 내부에서 변경하기 때문에, 변경되는 부분을 하나로 모는게 훨씬더 디자인 측면에서 좋은 코드이다.

위의 디자인기법 같은 경우 재밌는 방법이니 잘 기억해두도록 하자! 😙

Outtro.

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

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