Chapter 4. 설계 및 선언 part1 항목 18 - 21
항목 18. 인터페이스 설계는 제대로 쓰기엔 쉽게, 엉터리로 쓰기엔 어렵게 하자.
-
인터페이스의 올바른 사용을 이끄는 방법으로는 인터페이스 사이의 일관성 잡아주기, 기본제공 타입과의 동작 호환성 유지하기가 있습니다.
-
사용자의 실수를 방지하는 방법
- 새로운 타입 만들기
1
2
3
4
5
6
7
8
9
10class Date {
public:
Date(int month, int day, int year); // 매개 변수 전달 순서가 잘못될 여지가 열려 있다.
...
};
class Date {
public:
Date(const Month& m, const Day& d, const Year& y); //타입을 통해 올바른 전달 순서를 보장한다.
};- 객체의 값에 대해 제약 걸기
1
2
3
4
5
6
7
8
9
10class Month { //월의 경우 1~12 사이값만이 유효합니다.
public:
static Month Jan() {return Month(1);}
static Month Feb() {return Month(2);}
...
private:
explicit Month(int m); // 객체가 외부에서 새로 생성되지 않게 private으로 선언합니다.
};
Date d(Month::Jan(), Day(30), Year(1995));enum
을 넣는 방법이 있지만 타입 안전성은 그리 믿음직하지 못합니다. 때로는int
처럼 쓰일 수도 있습니다.- 타입에 대한 연산을 제한하기 (ex)
const
사용) - 자원 관리 작업을 사용자 책임으로 놓지 않기 (ex)스마트 포인터 사용)
항목 19. 클래스 설계는 타입 설계와 똑같이 취급하자
- 설계를 위한 고려사항
- 새로 정의한 타입의 객체 생성 및 소멸은 어떻게 이루어져야 하는가?
클래스 생성자 및 소멸자 설계 - 객체 초기화는 객체 대입과 어떻게 달라야 하는가?
생성자와 대입 연산자의 동작 및 둘 사이의 차이점 - 새로운 타입으로 만든 객체가 값에 의해 전달되는 경우에 어떤 의미를 줄 것인가?
복사 생성자 - 새로운 타입이 가질 수 있는 적법한 값에 대한 제약은 무엇으로 잡을 것인가?
클래스의 불변속성(invariant) : 클래스의 데이터 멤버의 몇 가지 조합 값만은 반드시 유효해야 한다. - 기존의 클래스 상속 계통망(inheritance graph)에 맞출 것인가?
- 어떤 종류의 타입 변환을 허용할 것인가?
암시적(implicitly), 명시적(explicit) 변환 구현 - 어떤 연산자와 함수를 두어야 의미가 있을까?
- 표준 함수들 중 어떤 것을 허용하지 말 것인가?
private
함수 - 새로운 타입의 멤버에 대한 접근권한을 어느 쪽에 줄 것인가?
- 선언되지 않은 인터페이스로 무엇을 둘 것인가?
타입이 제공할 보장의 종류 - 새로 만드는 타입이 얼마나 일반적인가?
템플릿에 대한 고찰 - 정말로 꼭 필요한 타입인가?
항목 20. '값에 의한 전달’보다는 ‘상수객체 참조자에 의한 전달’ 방식을 택하는 편이 대개 낫다.
- 대체적으로 효율적일 뿐만 아니라 복사손실 문제까지 막아 줍니다.
복사손실 문제(slicing problem) : 파생 클래스 객체가 기본 클래스 객체로서 전달되는 경우 기본 클래스의 복사 생성자가 호출되고 파생 클래스 객체로 동작하게 해 주는 특징들이 ‘싹둑 잘려’ 떨어지고 맙니다. - 값에 의한 전달이 괜찮은 유일한 타입은 기본제공 타입, STL 반복자, 함수 객체타입 뿐입니다.
항목 21. 함수에서 객체를 반환해야 할 경우에 참조자를 반환하려고 들지 말자.
- 지역 스택 객체에 대한 포인터나 참조자를 반환하는 일, 혹은 힙에 할당된 객체에 대한 참조자를 반환하는 일, 또는 지역 정적 객체에 대한 포인터나 참조자를 반환하는 일은 그런 객체가 두 개 이상 필요해질 가능성이 있다는 절대로 하지 마세요.
1 | Rational w, x, y, z; |
- 지역 스택 객체에 대한 포인터나 참조자
함수가 끝날때 객체가 소멸되기에 잘못된 메모리를 가르키고 있는 참조자/포인터입니다. - 힙에 할당된 객체에 대한 참조자를 반환하는 일
위와 같이 연산이 한문장에서 두번 이뤄질 때 메모리 해재에 대한 보장이 이뤄지지 못합니다. - 지역 정적 객체에 대한 포인터나 참조자를 반화하는 일
static
을 사용하여 다중 쓰레드 환경에서 보장되지 못할 수 있고 단적인 예로if((a*b) == (c*d))
의 값이 항상 참이 나오는 오류를 범할 수 있습니다.