[C++] 항목 24: 타입 변환이 모든 매개변수에 대해 적용되어야 한다면 비멤버 함수를 선언하자.

업데이트:     Updated:

카테고리:

태그:

이 글은 아래의 책을 자세히 정리한 후, 정리한 글을 GPT에게 요약을 요청하여 작성되었습니다.
이펙티브 C++ 제3판, 스콧 마이어스 저자, 곽용재 번역

📦 4. 설계 및 선언

👉🏻 항목 24: 타입 변환이 모든 매개변수에 대해 적용되어야 한다면 비멤버 함수를 선언하자.

아래는 유리수를 나타내는 클래스를 구현한 코드이다:

class Rational {
public:
	// int->Rational 암시적 변환을 위해,
	// explicit 을 붙이지 않았다.
	Rational(int numerator = 0, int denominator = 1);

	int numerator() const;
	int denominator() const;

	// 연산을 멤버 함수에 선언함
	const Rational operator*(const Rational& rhs) const;

private:
	...
};

유리수의 곱셈과 같은 연산은 멤버 함수에 선언해야 자연스러워 보인다.
그런데 항목 23에서 배웠던 것을 생각해보면 비멤버 비프렌드 함수로 두어야 할 것 같기도 하다.

우선 아래를 살펴보자.


✅ operator* 함수

Rational oneEighth(1, 8);
Rational oneHalf(1, 2);

Rational result = oneHalf * oneEighth; // 성공
result = result * oneEighth; // 성공

위의 코드는 올바르게 작동한다.

이번엔 혼합형 수치 연산을 지원하고 싶다.
유리수 클래스인 Rational과 int를 곱해보자.

result = oneHalf * 2; // 성공
result = 2 * oneHalf; // 실패

여기서 성공하는 이유는 무엇이고, 실패하는 이유는 무엇일까?


1. result = oneHalf * 2;

이 코드를 함수 형태로 바꾸면 다음과 같다:

result = oneHalf.operator*(2)

그러나 이상한 점이 있다.

해당 연산자의 인자는 Rational 객체의 참조가 들어가야 한다.
마치 Rational temp = 2;와 같은 동작을 하도록 한 것이다.

어떻게 가능한 것일까?

바로, 암시적 타입 변환(implicit type conversion)을 통해 가능하다.

중요한 점은 Rational 클래스의 생성자가 명시호출(explicit)로 선언되지 않았다는 것.
→ 그래서 컴파일러는 암시적으로 int를 Rational로 변환할 수 있게 된다.

정리하면 아래와 같이 동작한다:

const Rational temp(2); // 임시 Rational 객체 생성
result = oneHalf * temp; // oneHalf.operator*(temp);

2. result = 2 * oneHalf;

당연히 문제가 발생한다.

이 코드를 함수 형태로 바꾸면 아래와 같다:

result = 2.operator*(oneHalf);

정수 2에는 operator* 멤버 함수 같은 것이 있을 리가 없다.

여기서 컴파일러는 비멤버 버전의 operator*를 찾아본다.

result = operator*(2, oneHalf); // 실패

이 역시 존재하지 않는 함수다.
→ 그래서 컴파일 에러가 발생한다.

이를 해결하려면 어떻게 해야 할까?


✅ 해결 방법

class Rational {
	... // Rational 안에 operator*를 지운다.
}

// 비멤버 함수로 선언하였다.
const Rational operator*(const Rational& lhs, const Rational& rhs) {
	return Rational(lhs.numerator() * rhs.numerator(),
									lhs.denominator() * rhs.denominator());
}

int main() {
	Rational oneFourth(1, 4); // 성공

	Rational result;
	result = oneFourth * 2; // 성공
	result = 2 * oneFourth; // 성공
}

operator*를 비멤버 함수로 만들고,
모든 인자에 대해 암시적 타입 변환을 수행할 수 있도록 만들면 된다.


❓ operator* 함수를 Rational 클래스의 프렌드로 두어도 될까?

이 함수에서는 그러면 안 된다.
→ operator*는 Rational의 public 인터페이스만을 사용하여 구현할 수 있기 때문

“멤버 함수의 반대는 프렌드 함수가 아닌, 비멤버 함수이다.” 라는 결론을 뽑을 수 있다.
또한, 프렌드 함수를 피할 수 있다면 피하도록 하자.


🧐 정리

  • 어떤 함수에 들어가는 모든 매개변수(this 포인터가 가리키는 객체 포함)에 대해 타입 변환을 해줄 필요가 있다면, 그 함수는 비멤버이어야 한다.
  • 멤버 함수의 반대는 프렌드 함수가 아닌 비멤버 함수이다.
  • 프렌드 함수를 피할 수 있다면 피하자.

Cpp 카테고리 내 다른 글 보러가기

댓글남기기