본문 바로가기

Effective C++/4. 설계 및 선언

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

클래스에서 암시적 타입 변환을 지원하는 것은 일반적으로 못된 생각이다.

하지만 예외도 있는데, 대표적인 경우가 숫자 타입을 만들 때이다.

 

예를들면 유리수를 나타내는 클래스를 만들고 있다면, 정수->유리수의 암묵적 타입 변환은 허용하자고 판단해도 된다. 왜냐하면 C++에서의 int -> double 변환과 별반 다를게 없기 때문이다.

 

 

 

Rational = 유리수를 나타내는 클래스

그럼 +,* 같은 기본연산은 지원해줘야 것이다. => 멤버함수, 비멤버함수, 비멤버-비프렌드함수 중에 뭘로 해야 할까

 

23에서 비멤버-비프렌드 함수를 쓰자고 배웠지만, 일단 멤버함수로 만들어보자.

 

이러면 왠만한 상황에선 * 것이다. 하지만 혼합형 수치 연산도 가능했으면 좋겠다. (mixed-mode 지원) => int 같은 것과도 곱하고 싶다.

 

일단 상태에선

 

r1 = r2 * 2; // good

r1 = 2 * r2; // error -> 2.operator*(r2) -> 없음 -> operator*(2,r2); -> 없음

 

근데 곱셈은 당연히 교환법칙 성립해야됨.

첫번째는 성공했나?

r2.operator(2) => Rational 객체인 r2 operator* 함수를 멤버로 가지고 있어서 호출함 -> 인자 2 어떻게 먹음? -> 암시적 타입 변환 때문이다.

 

컴파일러는 우리가 함수에 int 넘겼고, 함수 쪽에선 Rational 요구한다는 사실을 안다. -> int Rational 생성자의 인자로 넘겨서 int->Rational 바꾸는 것이 가능하다는 것도 안다. -> 코드로 반환됨

 

const Rational temp(2);

result = oneHalf * temp;

 

물론 컴파일러가 이렇게 동작한 것은 생성자가 명시호출(explicit) 아니였기 때문이다. 만약 explicit이였으면 2 Rational(2) 암묵적으로 바꾸는 것이 불가능함.

 

 

explicit으로 하면

 

에러남;

 

대신 일관성이 있게 에러가 난다. 하나라도 건지는 보다 일관성을 챙기는게 중요함

일관성은 챙겼다. 그럼 연산이 되게 해야된다. 어떻게??

 

이렇게 하면 되지;

 

r1 = r2 * 2; // good

r1 = 2 * r2; // error -> 2.operator*(r2) -> 없음 -> operator*(2,r2); -> 없음

 

1번은 되고 2번은 안되는데 => 암시적 타입 변환이 일어날려면 매개변수 리스트에 있어야 된다. 두번째 문장에서 2 2.operator*(r2) 에선 객체 취급(매개변수 자체가 아님) , operator*(2,r2) 에선 이에 맞는 비멤버 함수가 없음(하지만 매개변수는 맞음) => 비맴버 함수를 그냥 만들면 된다.

 

 

 

 

이때 비맴버 함수라 데이터 맴버에 접근을 못하니, 데이터 맴버의 값을 반환해주는 examiner 함수를 통해 값을 불러옴(함수를 사용하여 멤버에 대한 접근성을 세밀하게 컨트롤 가능, 항목 22 참조)

 

friend 쓰면 되지 않나 -> 이미 public으로 완벽히 구현 가능한데 굳이?

 

friend 솔직히 안쓰는게 정신건강에 좋음. 가끔 반드시 써야할때가 있긴함(static function으로 구현이 안될 , iostream operator>> 같은 ) 하지만 맴버함수의 반댓말은 비맴버함수이다. 

 

 

맴버함수 vs 정적 맴버함수 vs 프랜드 함수 vs 비맴버-비프랜드함수

 

차이를 명확하게 알자

 

 

======================================================================

 

  • 어떤 함수에 들어가는 모든 매개변수(this 가르키는 객체 포함) 대해 타입 변환을 필요가 있다면, 함수는 비멤버여야 한다.