항목 18. 인터페이스 설계는 제대로 쓰기엔 쉽게, 엉터리로 쓰기엔 어렵게 하자
- 인터페이스를 설계할 때 최소한의 제약 ( 타입, ... )을 갖자. (?)
- 좋은 인터페이스는 제대로 쓰기에 쉬우며 엉터리로 쓰기에 어렵습니다. 인터페이스를 만들때는 이 특성을 지닐 수 있도록 고민하고 또 고민합시다.
- 인터페이스의 올바른 사용을 이끄는 방법으로는 인터페이스 사이의 일관성 잡아주기, 그리고 기본 제공 타입과의 동작 호환성을 유지하기가 있습니다.
- 사용자의 실수를 방지하는 방법으로는 새로운 타입 만들기, 타입에 대한 연산자 제한하기, 객체의 값에 대한 제약 걸기, 자원 관리 작업을 사용자 책임으로 놓지 않기가 있습니다.
- tr1::shared_ptr은 사용자 정의 삭제자를 지원합니다. 이 특징 때문에 tr1::shared_ptr은 교차 DLL문제를 막아주며, 뮤텍스 등을 자동으로 잠금 해제하는데 쓸수 있습니다.
항목 19. 클래스 설계는 타입 설계와 똑같이 취급하자.
- 좋은 타입은 문법이 자연스럽고, 의미구조(semantics)가 직관적이며, 효율적인 구현이 한가지 이상 가능해야 한다.
- 타입 설계시 고려할 사항
- 새로 정의한 타입의 객체 생성 및 소멸은 어떻게 이루어져야 하는가?
- 객체 초기화는 객체 대입과 어떻게 달라야 하는가?
- 새로운 타입으로 만든 객체가 값에 의해 전달되는 경우에 어떤 의미를 줄 것인가?
- 새로운 타입이 가질 수 있는 적법한 값에 대한 제약은 무엇으로 잡을 것인가?
- 기존의 클래스 상속 계통망(inheritance graph)에 맞출 것인가?
- 어떤 종류의 타입 변환을 허용할 것인가?
- 어떤 연산자와 함수를 두어야 의미가 있을까?
- 표준 함수들 중 어떤 것을 허용하지 말 것인가?
- 새로운 타입의 멤버에 대한 접근권한을 어느 쪽에 줄 것인가?
- '선언되지 않은 인터페이스'로 무엇을 둘 것인가?
- 새로 만드는 타입이 얼마나 일반적인가? ( 일반화 template )
- 정말로 꼭 필요한 타입인가?
- 클래스 설계는 타입 설계입니다. 새로운 타입을 정의하기 전에, 이번 항목에 나온 모든 고려사항을 빠짐없이 점검해 보십시오.
항목 20. '값에 의한 전달'보다는 '상수객체 참조자에 의한 전달'방식을 택하는 편이 대개 낫다.
- 기본적으로 함수에 객체를 전달할 때 '값에 의한 전달' 방식을 사용한다. (C에서 물려받은 특성)
- 값에 의한 전달인 경우 복사 생성자, 소멸자 를 호출하기 때문에 효율이 떨어진다.
- 참조에 의한 전달방식은 복사손실 문제가 없어지는 장점도 있다.
- 참조자는 내부적으로 포인터 사용.
- 반복자, 함수 객체 만들때 규칙
- 복사 효율을 높일것
- 복사손실 문제에 노출되지 않도록 만드는 것
- 사용자 정의 타입은 참조 전달을 사용하자.
- 기본 타입과 사용자 정의 타입은 아예 다른게 취급 되는 경우도 있다 ( 이럴경우 4byte 객체라고 하더라도 참조로 전달하자. )
- 사용자 정의 타입은 언제든 변화에 노출되어 있다.
- 기본 제공 타입, STL 반복자, 함수 객체 타입 이외에는 참조 전달 방식을 사용하라.
- '값에 의한 전달'보다는 '상수 객체 참조자에 의한 전달'을 선호합시다. 대체적으로 효율적일뿐만 아니라 복사손실 문제까지 막아 줍니다.
- 이번 항목에서 다룬 법칙은 기본제공 타입 및 STL 반복자, 그리고 함수 객체 타입에는 맞지 않습니다. 이들에 대해서는 '값에 의한 전달'이 더 적절합니다.
항목 21. 함수에서 객체를 반환해야 할 경우에 참조자를 반환하려고 들지 말자.
- 참조자는 객체(인스턴스)에 또 다른 이름일 뿐이다.
- 지역 객체의 포인터나 레퍼런스를 반환 하지 마라. ( 리턴할 때 객체가 사라진다. 쓰레기 주소 반환 )
- 새로운 객체를 반환하게 만들자. (반환값 최적화[return value optimization] RVO)
- 지역 스택 객체에 대한 포인터나 참조자를 반환하는 일, 혹은 힙에 할당된 객체에 대한 참조자를 반환하는 일, 또는 지역 정적 객체에 대한 포인터나 참조자를 반환하는 일은 그런 객체가 두 개 이상 필요해질 가능성이 있다면 절대로 하지 마세요.
항목 22. 데이터 멤버가 선언될 곳은 private 영역임을 명심하자.
- 문법적 일관성. 어떤 객체에 접근 할 수 있는 유일한 수단은 멤버 함수이다.
- 세밀한 접근 제어. 캡슐화.
- 클래스의 불변성 및 사전조건, 사후조건을 검증할 수 있다. [무결성] ( delphi, C# - property )
- protected 도 public 과 다르지 않다. 캐슐화 되지 않았다.
- 데이터 멤버는 private 멤버로 선언합시다. 이를 통해 클래스 제작자는 문법적으로 일관성 있는 데이터 접근 통로를 제공할 수 있고, 필요에 따라서는 세밀한 접근 제어도 가능하며, 클래스의 불변속성을 강화할 수 있을 뿐 아니라, 내부 구현의 융통성도 발휘할 수 있습니다.
- protected는 public보다 더 많이 '보호'받고 있는 것이 절대로 아닙니다.
항목 23. 멤버 함수보다는 비멤버 비프렌드 함수와 더 가까워지자.
- 비멤버 함수를 사용하면 패키징 유연성이 높아지는 장점이 있으며, 추가적으로 컴파일 의존도도 낮추고 확장성을 높일 수 있습니다.
- 캡슐화하면 유연성(융통성)이 증가한다. ( 당연 -_-; )
- 꼭 비멤버 비프렌드이여야 한는것 아니고, 그 클래스의 private 멤버의 캡슐화에 영향을 주지 않는다는 점이 중요하다.
- 이런한 편의 함수들을 같은 네이스페이스의 함수로 만들면 더 자연스럽다.
- 네임스페이스로 할 경우, 여러개의 소스 파일로 나누어 만들수 있는 장점도 있다. ( C++ standard library )
- 클래스 하나를 나누어 구현 할수 없다. ( C# 가능 )
- 멤버 함수보다는 비멤버 비프렌드 함수를 자주 쓰도록 합시다. 캡슐화 정도가 높아지고, 패키징 유연성도 커지며, 기능적인 확장성도 늘어납니다.
항목 24. 타입 변환이 모든 매개변수에 대해 적용되어야 한다면 비멤버 함수를 선언하자.
- 암시적 타입변환은 꼭 필요한 곳에 사용하자.
- 멤버 함수의 반대는 프렌드 함수가 아니라 비멤버 함수이다.
- 어떤 함수에 들어가는 모든 매개변수(this 포인터가 가리키는 객체도 포함해서)에 대해 타입 변환을 해 줄 필요가 있다면, 그 함수는 비멤버이어야 합니다.
항목 25. 예외를 던지지 않는 swap에 대한 지원도 생각해 보자.
- 표준 라이브러리 swap 알고리즘.
- pimpl (pointer to implementation) 기법을 사용하면 설계, 비용 모두 좋다.
- 완전 템플릿 특수화(total template specialization)를 이용하라.
- C++은 클래스 템플릿에 대해서는 부분 특수화(partial specialization)는 허용하지만 함수 템플릿에 대해서는 허용하지 않는다.
- 인자 기반 탐색(argument-dependent lookup) 혹은 쾨니그 탐색(koenig lookup).
- namespace를 사용하라.
- 특수화 버전을 만들었을 수도 있기때문에 호출문에 한정자를 쓰지 말자.
- 강력한 예외 안전성 보장(strong exception-safety guarantee)을 제공하자.
- std::swap이 여러분의 타입에 대해 느리게 동작할 여지가 있다면 swap 멤버 함수를 제공합시다. 이 멤버 swap은 예외를 던지지 않도록 만듭시다.
- 멤버 swap을 제공했다면, 이 멤버를 호출하는 비멤버 swap도 제공합시다. 클래스(템플릿이 아닌)에 대해서는 std::swap도 특수화해 둡시다.
- 사용자 입장에서 swap을 호출할때는, std::swap에 대한 using 선언을 넣어 준후에 네이스페이스 한정 없이 swap을 호출합시다.
- 사용자 정의 타입에 대한 std 템플릿을 완전 특수화하는 것은 가능합니다. 그러나 std에 어떤 것이라도 새로 '추가'하려고 들지는 마십시오.
이 글은 스프링노트에서 작성되었습니다.
댓글 없음:
댓글 쓰기