객체지향프로그래밍(22) - 연산자 오버로딩(1), 단항 연산자와 산술 연산자

단항 연산자와 산술 연산자의 오버로딩

Featured image

🔚 짧게 하는 복습

✅ 1. 가상 상속의 정의와 활용을 안다.

✅ 2. 가상 상속을 통해 다이아몬드 문제의 부분 해결을 할 수 있다.

✅ 3. 하지만 가상 상속을 해도 여전히 남아있는 문제에 대해서 안다.

✅ 4. 그러나 왜 C++에서 해결될 수 있었는지 알아본다.

혹시 기억이 안 난다면, 다시 돌아가자


저번 시간은 virtual 키워드에 대해서 다루었다.

혹시 배운 지가 오래됐지만, 함수 오버로딩에 대해 기억하는가?

함수 오버로딩은 매개변수의 개수가 다른 경우 함수를 재정의하는 것을 말했다.

그런데 함수와 비슷한 형태 중 우리가 의식도 하지 않고 너무 당연하게 사용하고 있는 것이 있다.

바로 연산자이다, 오늘은 연산자를 오버로딩하는 기술을 배운다.


연산자 오버로딩이란?

연산자 오버로딩사용자가 정의한 클래스나 구조체에 대해 내장된 연산자들을 새로운 의미로 재정의하는 것을 말한다.

C++에서 많은 연산자가 이미 기본적으로 정의되어 있다.

예를 들어, 산술 연산자(+, -, *, /), 비교 연산자(==, !=, <, >, <=, >=), 할당 연산자(=) 등이 있다.

하지만 이러한 연산자들은 C++의 기본 데이터 타입(int, double, 등)에 대해서만 사용할 수 있고, 사용자 정의 구조체나 클래스에서는 사용할 수 없다.

예를 들어, 아래와 같다는 뜻이다.

class counter{
    int count;

    public:
    counter() : count(0) {}
};

int main(){
    int a = 3;
    int b = 4;

    int c = a+ b; // 이건 되지만

    counter a, b, c;

    c = a+ b; // 이런 건 안됨
}

즉, 저런 경우에 counter라는 클래스에 대해 연산자들오버로딩해줄 필요가 있다.

그런데, 각각의 연산자들의 오버로딩 방식이 조금씩 다르다보니 하나하나씩 실습 코드와 예제를 보는 방식으로 진행해보겠다.


단항 연산자 ++, –

단항 연산자는 ++, – 총 4가지가 있다.

왜 4가지냐고? 전위(prefix)와 후위(postfix)가 나뉘기 때문이다.

오버로딩이다 보니, 매개 변수를 바꿔줘야 하는데 둘을 어떻게 구분할까?

C++에서는 전위 연산매개변수를 주지 않고, 후위 연산int 자료형 매개변수를 주는 방식으로 해결했다.

아래 예제 코드를 직접 따라 해보고 실행해보자.

이는 전위 연산을 오버로딩한 코드이다. 보이다시피, 전위 연산 오버로딩매개변수를 주지 않는다.

자, 여기서 조금 코드를 수정하면 이런 것도 가능하다.

생성자 자체를 반환하는 것으로 대입, 메소드 직접 호출 등 더 유연한 코딩이 가능하다.

여기서 후위 연산자를 추가하면 똑같은 코드에 int 매개변수만 넣어줘서 만들면 된다.

이 코드에서 int 매개변수는 아무 의미 없으며, 전위 연산자와 구별하기 위함이다.

++가 아니라 –를 해도 같음을 직접 확인할 수 있다. (과제로 남기겠다.)


이항 연산자 - 산술 연산자(+, -, *, /)

이번엔 이항 연산자이다.

이항 연산자하나의 클래스 혹은 구조체를 매개변수로 받아서 호출된 객체의 값과 매개변수로 받은 값을 연산한다.

아래와 같은 코드가 된다는 소리이다.

이 코드에서 조금 수정할 수 있는데,

첫 번째는 const를 이용해서 피연산항은 수정되지 않게 할 수 있다.

counter operator + (counter c2) const

두 번째는 범위 연산자 ::를 이용해서 구체적인 구현 사항은 밖으로 빼서, 클래스를 가볍게 만들 수 있다.

두 방법을 적용한 아래 코드를 보자.

훨씬 간단하게 class의 내용을 파악할 수 있고, const를 이용해서 더 안정적으로 만들었다.

그런데 + 연산자를 이용하면 왼쪽 객체에서 호출되는 걸까? 아니면 오른쪽 객체에서 호출되는 걸까?


산술 연산자의 호출 원리

흥미로운 실험이 있는데, 아래와 같은 코드를 실행시켜보자.

counter cnt3 = cnt+ 3;
cnt3.print_cnt();

//counter cnt4 = 3 + cnt2; 이건 안됨
//cnt4.print_cnt();

어떻게 위는 실행되는 것이고, 아래는 안되는 것일까?

그 이유는 산술 연살자는 기본적으로 왼쪽 객체가 호출하기 때문이다.

그리고 오버로딩 연산자는 매개변수에 들어오면 안 되는 기본 자료형이 들어오면, 생성자를 실행시켜 객체로 바꾼다.

이게 무슨 말이냐면 위에서 적은 코드는 아래와 같다는 뜻이다.

counter cnt3 = cnt+counter(3);
counter cnt4 = 3 + cnt2;

이제는 왜 위만 되는지 명확히 보인다.

위쪽은 객체 + 객체의 연산으로 바뀌고, 아래는 여전히 정수 + 객체이기 때문이다.

이런 형변환을 암시적 형변환(implicit casting)이라고 하고, C++가 제공하는 편리한 기능 중 하나이다.


📖 오늘의 핵심(다 알기 전까지는 넘어가지 말자❗)

✅ 1. 단항 연산자의 오버로딩을 할 수 있다.

✅ 2. 산술 연산자의 오버로딩을 할 수 있다.

✅ 3. 오버로딩 산술 연산자의 호출 원리를 이해한다.

⚠️ 많은 코드에서 가독성을 위해 범위 지정자:: 를 통해 클래스 밖에서 정의한다.

⚠️ 암시적 형변환은 가끔 의도치 않은 버그를 일으키는 주범이 된다.

💣 과제,

  1. 단항 연산자 –의 prefix와 postfix를 구현해보자. (난이도 下)

  2. 나머지 산술 연산자(+, -, *, /, %) 파트도 구현해보자. (난이도 下)