Programming Language/C/C++

[C++] 연산자 오버로딩과 프렌드

깐요 2017. 6. 1. 11:51

연산자 오버로딩이란?

=, +, -, * 등과 같은 C++의 표준 연산자를 클래스 객체에 사용할 수 있게 해주는 것이다

즉 하나의 연산자에 다양한 의미를 부여할 수 있다

그렇다면 왜 오버로딩을 하면서 사용해야 할까?

<< 연산자를 생각해보자

<<는 본래 비트 연산자로 쉬프트 기능을 한다

하지만 cout을 통한 출력 명령을 사용할 때도 << 연산자를 사용한다

이런 식으로 코드를 좀 더 자연스럽게 만들어준다


어떻게 오버로딩을 할까?

시간과 분의 값을 가지는 Time이라는 클래스의 객체 work와 break가 있다고 해보자

work와 break의 총 시간을 알고 싶을 때 어떻게 해야할까

보통은 Time 클래스에 void Add(cosnt Time &t)와 같은 멤버 함수를 정의하여 사용할 것이다

하지만 work.Add(break)를 호출하는 것보다 + 연산자를 오버로딩하여 사용하는 쪽이 알아보기도 쉽고 사용이 간편하다


연산자 함수의 형식은 다음과 같다

operator op(argument-list)

op는 오버로딩할 연산자를 나타내는 기호다

위에서 다뤘던 예제를 한번 실전에서 연습해보자

//mytime.h
class Time
{
    ...
public:
    ...
    Time operator+(const Time &t) const;
    ...
}
 
//mytime.cpp
...
Time Time::operator+(const Time &t) const
{
    Time sum;
    sum.minutes = minutes + t.minutes;
    sum.hours = hours + t.hours + sum.minutes / 60;
    sum.minutes %= 60;
    return sum;
}
...



+ 연산자를 이용하여 Time 클래스 객체를 더하는 연산자로 오버로딩했다

메인 함수에서 work + break와 같은 명령문을 사용할 수 있다


그렇다면 work1 + break + work2도 가능할까?

이 궁금증은 + 연산자가 어떻게 동작하는지를 보면 알 수 있다

work + break는 다음과 같다

work.operator+(break)

즉 work1 + break + work2는

work1.operator+(break + work2)

와 같고 이는

work1.operator+( break.operator+(work2) )

와 같다

따라서 옳바르게 동작한다


work * 2는 가능한데 2 * work는?

시간을 2배로 늘리는 기능으로 * 연산자를 오버로딩하면 다음과 같다

//mytime.h
class Time
{
    ...
public:
    ...
    Time operator*(const double n) const;
    ...
}
 
//mytime.cpp
...
Time Time::operator*(const double n) const
{
    Time result;
    long totalminutes = hours * n * 60 + minutes * n;
    result.hours = totalminutes / 60;
    result.minutes = totalminutes % 60;
    return result;
}
...


work 시간을 2배로 늘리려면 work * 2 명령을 사용하면 된다 (작업 시간이 2배나 늘었다...)

그렇다면 2 * work도 될까?

답은 'No'다

위의 + 연산자는 된다 하지만 * 연산자는 안되는데 왜 안될까?

둘의 멤버 함수 정의를 보면 차이점이 보인다

+ 연산자는 전달 인자로 Time 객체를 참조받고

* 연산자는 전달 인자로 기본 데이터형인 double 데이터형을 참조받는다

이전에 살펴봤던 오버로딩 동작 방식을 다시 한 번 떠올려보면 궁금증이 해결된다

work * 2는

work.operator*(2)

로 문제 없지만 2 * work는

2.operator*(work)

으로 상당히 문제가 있어보인다

우선 operator*는 Time 클래스의 객체의 메세지를 받고 호출된다

하지만 2 * work는 double 데이터형의 메세지를 받고 호출하는 모양이므로 옳지 않다

그리고 전달 인자로 Time 클래스의 객체이므로 함수 정의와 어긋난다


이러한 문제는 오버로딩을 한번 더 하면 해결된다

그럼 위에서 배웠던 것을 참고로 * 연산자를 오버로딩해보자

안타깝지만 위와 같은 방법은 잘못됐다

Time operator*(const double n, const Time &t) const;

* 연산자는 2개의 피연산자를 통해 연산되는 것인데 위 함수 원형은 3개의 피연산자를 인식한다

이를 해결하기 위해서는 멤버가 아닌 함수를 사용하면 되는데 프렌드라는 개념을 알아야 한다


프렌드? friend?

프렌드는 클래스의 멤버 함수들이 가지는 것돠 동등한 접근 권한을 갖게 해준다

즉 프렌드 함수는 멤버 함수는 아니면서도 클래스의 private 멤버에 접근할 수 있다

프렌드를 사용하여 위 문제를 해결해보자

class Time
{
    ...
public:
    ...
    friend Time operator*(const double n, const Time &t)
    { return t * n; }
    ...
};


위 함수 원형은 다음 두가지 함축적 의미를 가지고 있다

operator*()함수는 클래스 선언 안에 선언되지만 멤버 함수가 아니기 때문에 멤버 연산자를 사용하여 호출되지 않는다

operator*()함수는 그것이 비록 멤버 함수는 아니지만 멤버 함수와 동등한 접근 권한을 가진다

위에서는 간단하게 이전에 오버로딩한 * 연산자를 사용하게 하는 인라인 함수로 만들었지만

함축적 의미를 확인해볼 수 있는 함수 정의는 다음과 같다


Time operator*(const double n, const Time &t)
{
    Time result;
    long totalminutes = t.hours * n * 60 + t.minutes * n;
    result.hours = totalminutes / 60;
    result.minutes = totalminutes % 60;
    return result;
}

정의에는 friend를 사용하지 않는다

그리고 Time:: 사용 범위 연산자를 사용하지 않는다

마지막으로 전달 받은 객체의 private 멤버를 멤버 연산자 .를 사용하여 직접 접근한다

위 함수는 멤버 함수가 아니기 때문에 Time 클래스 객체가 아니더라도 함수를 사용할 수 있다

즉 2 * work 와 같은 명령문을 사용할 수 있게 된다



여담으로 cout(wcout)에 사용되는 << 연산자를 오버로딩할 때는 ostream(wostream)을 리턴하는 것이 바람직하다

만약 아무것도 리턴하지 않는다면 cout << work << break와 같은 명령문은 오류를 발생시킨다

그 이유는 역시 동작 과정을 알아보면 이해할 수 있다

operator<<(cout, work << break)

operator<<(cout, ?)

리턴 값이 없다면 ? 부분에서 오류가 생기기 때문이다


320x100