득이공간

[Effective C++] 4장. 설계 및 선언 본문

PS/C++

[Effective C++] 4장. 설계 및 선언

쟁득 2025. 3. 5. 12:26
해당 게시물은 프로텍 미디어의 'Effective C++'를 읽고
학습한 내용을 개인적으로 요약한 글입니다.

📌 목차 - 4장. 설계 및 선언

4-1. 인터페이스 설계는 제대로 쓰기엔 쉽게, 엉터리로 쓰기엔 어렵게 하자

4-2. 클래스 설계는 타입 설계와 똑같이 취급하자

4-3. ‘값에 의한 전달’ 보다는 ‘상수객체 참조자에 의한 전달’ 방식을 택하는 편이 대개 낫다

4-4. 함수에서 객체를 반환해야 할 경우에 참조자를 반환하려고 들지 말자

4-5. 데이터 멤버가 선언될 곳은 private 영역임을 명심하자

4-6. 멤버 함수보다는 비멤버 비프렌드 함수와 더 가까워지자

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

4-8. 예외를 던지지 않는 swap에 대한 지원도 생각해 보자


📌 4-1. 인터페이스 설계는 제대로 쓰기엔 쉽게, 엉터리로 쓰기엔 어렵게 하자

  • 좋은 인터페이스는 제대로 쓰기에 쉬우며 엉터리로 쓰기에 어렵다. 인터페이스를 만들 때는 이 특성을 지닐 수 있도록 고민하고 또 고민하자.
  • 인터페이스의 올바른 사용을 이끄는 방법으로는 인터페이스 사이의 일관성 잡아주기, 그리고 기본제공 타입과의 동작 호환성 유지하기가 있다.
  • 사용자의 실수를 방지하는 방법으로는 새로운 타입 만들기, 타입에 대한 연산을 제한하기, 객체의 값에 대해 제약 걸기, 자원 관리 작업을 사용자 책임으로 놓지 않기가 있다.
  • tr1::shared_ptr는 사용자 정의 삭제자를 지원한다. 이 특징 때문에 tr1::shared_ptr는 교차 DLL 문제를 막아주며, 뮤텍스 등을 자동으로 잠금 해제하는 데 쓸 수 있다.

📌 4-2. 클래스 설계는 타입 설계와 똑같이 취급하자

  • 클래스 설계는 타입 설계다. 새로운 타입을 정의하기 전에, 다음 고려사항을 모두 점검하자.
    • 새로 정의한 타입의 객체 생성 및 소멸은 어떻게 이루어져야 하는가?
    • 객체 초기화는 객체 대입과 어떻게 달라야 하는가?
    • 새로운 타입으로 만든 객체가 값에 의해 전달되는 경우에 어떤 의미를 줄 것인가?
    • 새로운 타입이 가질 수 있는 적법한 값에 대한 제약은 무엇으로 잡을 것인가?
    • 기존의 클래스 상속 계통망(inheritance graph)에 맞출 것인가?
    • 어떤 종류의 타입 변환을 허용할 것인가?
    • 어떤 연산자와 함수를 두어야 의미가 있을까?
    • 표준 함수들 중 어떤 것을 허용하지 말 것인가?
    • 새로운 타입의 멤버에 대한 접근권한을 어느 쪽에 줄 것인가?
    • ‘선언되지 않은 인터페이스’로 무엇을 둘 것인가?
    • 새로 만드는 타입이 얼마나 일반적인가?
    • 정말로 꼭 필요한 타입인가?

📌 4-3. ‘값에 의한 전달’ 보다는 ‘상수객체 참조자에 의한 전달’ 방식을 택하는 편이 대개 낫다

  • ‘값에 의한 전달’보다는 ‘상수 객체 참조자에 의한 전달’을 선호하자. 대체적으로 효율적일 뿐만 아니라 복사손실 문제까지 막아 준다.
  • 하지만 기본제공 타입 및 STL 반복자, 그리고 함수 객체 타입에 대해서는 ‘값에 의한 전달’이 더 적절하다.

📌 4-4. 함수에서 객체를 반환해야 할 경우에 참조자를 반환하려고 들지 말자

  • 지역 스택 객체에 대한 포인터나 참조자를 반환하는 일, 혹은 힙에 할당된 객체에 대한 참조자를 반환하는 일, 또는 지역 정적 객체에 대한 포인터나 참조자를 반환하는 일은 그런 객체가 두 개 이상 필요해질 가능성이 있다면 절대로 하지 말자.

📌 4-5. 데이터 멤버가 선언될 곳은 private 영역임을 명심하자

  • 데이터 멤버는 private 멤버로 선언하자. 이를 통해 클래스 제작자는 문법적으로 일관성 있는 데이터 접근 통로를 제공할 수 있고, 필요에 따라서는 세밀한 접근 제어도 가능하며, 클래스의 불변속성을 강화할 수 있을 뿐 아니라, 내부 구현의 융통성도 발휘할 수 있다.
  • protected는 public보다 더 많이 ‘보호’받고 있는 것이 절대로 아니다.

📌 4-6. 멤버 함수보다는 비멤버 비프렌드 함수와 더 가까워지자

  • 멤버 함수보다는 비멤버 비프렌드 함수를 자주 쓰도록 하자. 캡슐화 정도가 높아지고, 패키징 유연성도 커지며, 기능적인 확장성도 늘어난다.

📌 4-7. 타입 변환이 모든 매개변수에 대해 적용되어야 한다면 비멤버 함수를 선언하자

  • 어떤 함수에 들어가는 모든 매개변수(this 포인터가 가리키는 객체도 포함해서)에 대해 타입 변환을 해 줄 필요가 있다면, 그 함수는 비멤버이어야 한다.

📌 4-8. 예외를 던지지 않는 swap에 대한 지원도 생각해 보자

  • std::swap이 우리가 정의한 타입에 대해 느리게 동작할 여지가 있다면 swap 멤버 함수를 제공하자. 이 멤버 swap은 예외를 던지지 않도록 만들자.
  • 멤버 swap을 제공했으면, 이 멤버를 호출하는 비멤버 swap도 제공하자. 클래스(템플릿이 아닌)에 대해서는, std::swap도 특수화해 두자.
  • 사용자 입장에서 swap을 호출할 때는, std::swap에 대한 using 선언을 넣어 준 후에 네임스페이스 한정 없이 swap을 호출하자.
  • 사용자 정의 타입에 대한 std 템플릿을 완전 특수화하는 것은 가능하다. 그러나 std에 어떤 것이라도 새로 ‘추가’하려고 들지는 말자.