[Effective C++] 항목 24 ~ 26
항목 24 : 타입 변환이 모든 매개변수에 대해 적용되어야 한다면 비멤버 함수를 선언하자
- 클래스에서 암시적 타입 변환을 지원하는 것은 일반적으로 안 좋다.
- 숫자 타입은 C++ 기본 암시적 변환과 별반 다르지 않으므로 허용
- 클래스 멤버 함수에서 사칙 연산 구현시 문제
- ex)
const Rational operator*(const Rational& rhs) const; result = oneHalf * oneEighth; result = oneHalf * 2; // 암시적 타입 변환 result = 2 * oneHalf; // 에러!!!!
- ex)
- 암시적 타입 변환에 대해 매개변수가 먹혀들려면 매개변수 리스트에 들어있어야 한다.
- 호출되는 멤버 함수를 갖고 있는(this가 가리키는) 객체에 해당하는 암시적 매개변수는 암시적 변환이 먹히지 않음
-
operator*를 비멤버 함수로 만들어 모든 인자에 대해 암시적 타입변환을 수행하도록 한다.
- 멤버 함수의 반대는 프렌드 함수가 아니라 비멤버 함수이다.
- 프렌드 함수는 되도록 피하자
- operator*를 public 인터페이스만으로 구현 가능하므로 프렌드 함수로 두지 말자
항목 25 : 예외를 던지지 않는 swap에 대한 지원도 생각해보자
- swap 함수 : 예외 안전성 프로그래밍의 감초 역할
- ex) 자가 대입 현상 대처 대표 매커니즘
- 기본적인 swap은 한번 호출에 복사가 세번 발생
- 다른 타입의 실제 데이터를 가리키는 포인터가 주성분인 타입은 복사하면 손해를 많이 본다.
- ex) pimpl 관용구 : 실제 데이터 클래스의 포인터 객체를 가지는 클래스를 만들어 복사시 효율을 도모한다.
- 포인터만 바꾸면 되므로 복사시 자원 소모가 적다.
- 특수한 객체를 맞바꿀 때는 일반적인 방법 대신 pImpl 포인터만 맞바꾸라 해준다.
- 특수화(specialize) : 템플릿 특수화를 통해 필요 부분만 swap
- ex)
namespace std { template<> void swap<Widget>(Widget& a, Widget& b) { .... } }
- 문제점) 내부 구현시 private 객체에 바로 접근이 안될 시
- ex)
- 클래스 내부에서 swap을 public으로 지원하며 그 안에서 std::swap을 통해 swap한다.
- ex)
void swap(Widget& other) { using std::swap; swap(pImpl, other.pImpl); }
- 문제점) 클래스가 아닌 클래스 템플릿일 경우 클래스 템플릿 부분 특수화는 허용이 되지만 함수 템플릿 부분 특수화는 허용이 되지 않아서 std::swap 특수화 실패(위에서는 완전 특수화를 했기 때문에 성공했다. 완전 특수화는 허용한다.)
- ex)
- 그냥 오버로드 버전을 추가한다. 그렇지만 std는 특별한 namespace.
- std에 새로운 템플릿 추가는 불가능하다. : 컴파일 / 실행은 되지만 결과가 미정이다.
- std::swap의 특수화 버전 / 오버로딩 버전으로 선언하지 않고 멤버 swap을 호출하는 비멤버 swap을 선언하여 해결한다.
C++의 이름 탐색 규칙(인자 기반 탐색 / 쾨니그 탐색)에 의해 std 버전보다 먼저 찾아내게 된다. 즉, 그 클래스와 동일한 네임 스페이스 안에 비멤버 swap으로 선언하자.
- 특수화(specialize) : 템플릿 특수화를 통해 필요 부분만 swap
- 인자 기반 탐색(쾨니그 탐색)(ADL) : 어떤 함수에 어떤 타입의 인자가 있으면, 그 함수의 이름을 찾기 위해 해당 타입의 인자가 위치한 네임스페이스 내부의 이름을 탐색해 들어간다는 규칙
- 사용할 수 있는 swap의 종류
- std에 있는 일반형 버전 : 확실히 존재한다.
- std의 일반형을 특수화한 버전 : 있을수도 없을수도 있다
- T 타입 전용 버전 : 있거나 없거나 어떤 네임스페이스 안에 있거나 없거나
- 확실히 1) 혹은 2)를 사용시키게 하기 위해서는 swap 사용 전에 ‘using std::swap;’을 써준다.
- std::swap(obj1, obj2); // 잘못된 호출 »- std의 swap을 제외한 모든 것을 무시. 더 딱 맞는 T 전용 버전의 존재 가능성을 무시하게 된다.
- 멤버 버전 swap은 절대 예외를 던지게 하지 말자!!!!
- 클래스(및 클래스 템플릿)가 강력한 예외 안전성 보장을 제공하는 방법에서 보장되어야 하는 사항(29)
항목 26 : 변수 정의는 늦출 수 있는 데까지 늦추는 근성을 발휘하자
- 생성자 / 소멸자를 호출하는 타입으로 변수 정의 시 물게 되는 비용 두가지
- 프로그램 제어 흐름이 변수의 정의에 닿을 때 생성자가 호출되는 비용
- 변수가 유효범위를 벗어날 때 소멸자가 호출되는 비용
- 변수가 정의되어도 예외가 발생하면 안 쓰이고 생성 / 소멸 비용을 부담해야 한다.
- 어떤 변수를 사용해야 할 때가 오기 전까지 그 변수의 정의를 늦추는 것은 기본이고, 초기화 인자를 손에 넣기 전까지 정의를 늦출 수 있는지도 둘러봐야 한다.
- 불필요한 기본 생성자 호출도 없애기 위해 변수를 정의함과 동시에 초기화(복사 생성자를 사용한다)
- 루프 안에서만 쓰이는 변수는 루트 안 / 루트 밖 어디에 선언할 것인가
- 루트 안에 정의 : 생성자 n번 + 소멸자 n번
- 루트 밖에 정의 : 생성자 1번 + 소멸자 1번 + 대입 n번
- 클래스 중 대입에 들어가는 비용이 생성자 / 소멸자 쌍보다 적게 나오는 경우가 있는데 그 경우 2번이 효율이 좋다. 그렇지 않으면 1번을 사용하자
- 1번 방법이 2번 방법보다 유효번위가 좁아서 이해도와 유지보수성이 좋다.
- 대입이 생성자 / 소멸자 쌍보다 비용이 덜 들고 전체 코드에서 수행 성능에 민감한 부분을 건드리는 중이라 생각하지 않는다면 1번 방법을 사용하자.