Coding/Unreal, C++

[C++ Prime Plus] 11. Working with Classes

Operator Overloading

  • Operator Overloading은 C++ Polymophism 중 하나다.
  • Operator Overloading을 통해 사용자에게 실제로 어떻게 이런 연산이 이뤄지는지 감출 수 있다. 추상화는 OOP의 목표 중 하나다.
  • Operator Overloading을 정의하는 문법은 다음과 같다.
operator op(argument-list)
  • 이 때, op는 새로운 symbol을 만들어낼 수 없다. 즉, operator@ 같은 건 만들 수 없다.
  • 아래 예시를 보자. 컴파일러는 기본적으로 operator 왼쪽에 있는 오브젝트에서 정의됐다고 예상한다.
district2 = sid + sara;
district2 = sid.operator+(sara); // same

Introducing Friends

  • C++은 private을 통해 data hiding을 구현했다. 이 때, friend 키워드는 외부에서 private에 접근할 수 있도록 허용한다. friend 키워드는 세 종류에 대해 걸 수 있다.
    • Friend functions
    • Friend classes
    • Friend member functions
  • Friend는 15 챕터에서 좀더 자세히 다룬다. 여기서는 operator overloading에서 쓰이는 friend만 살펴본다.
  • 먼저 어떤 경우에 friend 키워드가 필요한지 알아보자.
A = B * 2.75; // == B.operator*(2.75);
A = 2.75 * B; // ???
  • 위 예시에서 첫줄의 연산이 된다면 둘째줄의 연산도 될 것 같지만 꼭 그렇지가 않다. 호출 함수가 다르기 때문이다.
  • 1 줄은 B.operator*(2.75)를, 2줄은 operator(2.75, B)를 호출한다. 연산자에 대한 정의는 연산자 왼쪽에 있는 오브젝트의 member function이거나 non-member function이어야 한다. 이 때, argument는 2개다.
Time operator*(double m, const Time& t)
{
	Time result;
	result.totalMinutes = t.totalMinutes * m; // totalMinutes가 private 변수라면??
    
    return result;
}
  • 문제는 실제로 구현할 때 나타난다. 이 operator overloading은 non-member function이기 때문에 Time 클래스의 totalMinutes에 접근할 수 없기 때문이다. 이를 해결하기 위해 friend 키워드를 쓸 수 있다. friend는 특정 함수, 클래스, 메소드에게 member function과 같은 정도의 access right를 부여한다.
// 아래 한 줄을 Time class에 추가한다. 
friend Time operator*(double m, const Time& t);

A Common Kind of Friend: Overloading the << operator

  • operator overloading에서 friend 키워드를 빈번하게 사용하게 되는 지점이 << operator다. 이 오버로딩은 주로 ostream 클래스와 연동된다. 가장 단순한 형태는 다음과 같을 것이다.
void operator<< (ostream& os, const Time& t)
{
	os << t.hours << " hours, " << t.minutes << " minutes";
}
  • 당연히 위 함수가 잘 돌아가려면 해당 오버로딩 함수가 time의 friend여야 할 것이다.
  • 사실, 위 구현은 큰 문제가 하나 있다. 아래 코드가 작동하지 않는 것이다.
cout << "Trip Time: " << trip << " (Tuesday)\n"; // can't do
  • 이유는 우리가 쓴 오버로딩 함수는 리턴값이 없기 때문이다. 위 코드가 돌아가려면 다시 ostream의 레퍼런스를 리턴해줘야 한다. (lvalue 레퍼런스이기 때문에 가능)
ostream& operator<< (ostream& os, Time t)
{
	...
    return os;
}

Overloaded Operators: Member Versus Nonmember Functions

  • 연산자 오버로딩을 member function으로 구현할지, non-member function으로 구현해야 할지는 프로그래머의 선택이다. 특정 연산자는 member function으로만 구현할 수 있지만 다른 경우엔 상관없다.
  • 주의해야 할 점은 둘 다 정의하면 안된다는 것이다. 그러면 컴파일러는 모호하다고 판단해서 에러를 준다.

Automatic Conversions and Type Casts for Classes

  • Class와 기본자료형 사이 형변환을 구현할 수 있다.
  • 놀랍게도! 우리는 이미 이 형변환을 구현했었다. 바로 생성자다.
class Stonewt
{
public:
	Stonewt(double lbs);
    ...
};
  • Stonewt 클래스는 double 하나를 받는 생성자가 있다. 이는 생성자일 뿐만 아니라 double에서 Stonewt로의 형변환이기도 하다. 즉, 아래 코드가 작동된다.
Stonewt myCat;
myCat = 19.6
  • 근데 위 코드는 myCat이 실수 자료형처럼 보이게 하는 등 혼동을 줄 수 있다. 그래서 이런 implicit conversion을 막아둘 수 있는데, 방법은 해당 생성자에 explicit 키워드를 맨 앞에 추가하는 것이다. explicit 키워드는 explicit conversion만 허용해주는 역할이다.
explicit Stonewt(double lb);
  • 이제 클래스→기본자료형을 보자. 문법은 아래와 같다.
operator typeName();
  • conversion function은 다음 3가지 특성을 가진다.
    • 메소드여야 한다.
    • 리턴 타입이 없어야 한다.
    • 매개 변수가 없어야 한다.
  • 위 특성을 지키는 구현부는 다음과 같을 것이다.
Stonewt::operator double() const
{
	return pounds;
}
  • 위 conversion function은 explicit이 아니니까 아래 코드는 동작할까?
Stonewt poppins;
...
double p_wt = poppins;
  • 정답은 '그럴 수도 있고 아닐 수도 있다'이다. 만약 Stonewt가 int conversion도 구현했다면 컴파일러는 에러를 준다. int로 형변화해야할지, double로 형변환해야할지 애매하기 때문이다. 그런데 만약 double conversion만 있다면 에러가 없다. 더이상 모호하지 않기 때문이다.

References

  • C++ Prime Plus 6th, Chapter 11