Chapter 4. 설계 및 선언 part1 항목 18 - 21

Effecive C++ 정리

Posted by Kyung Jun Cha on 2019-11-07

Chapter 4. 설계 및 선언 part1 항목 18 - 21


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

  • 인터페이스의 올바른 사용을 이끄는 방법으로는 인터페이스 사이의 일관성 잡아주기, 기본제공 타입과의 동작 호환성 유지하기가 있습니다.

  • 사용자의 실수를 방지하는 방법

    1. 새로운 타입 만들기
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    class Date {
    public:
    Date(int month, int day, int year); // 매개 변수 전달 순서가 잘못될 여지가 열려 있다.
    ...
    };

    class Date {
    public:
    Date(const Month& m, const Day& d, const Year& y); //타입을 통해 올바른 전달 순서를 보장한다.
    };
    1. 객체의 값에 대해 제약 걸기
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    class 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처럼 쓰일 수도 있습니다.

    1. 타입에 대한 연산을 제한하기 (ex)const 사용)
    2. 자원 관리 작업을 사용자 책임으로 놓지 않기 (ex)스마트 포인터 사용)

항목 19. 클래스 설계는 타입 설계와 똑같이 취급하자


  • 설계를 위한 고려사항
  1. 새로 정의한 타입의 객체 생성 및 소멸은 어떻게 이루어져야 하는가?
    클래스 생성자 및 소멸자 설계
  2. 객체 초기화는 객체 대입과 어떻게 달라야 하는가?
    생성자와 대입 연산자의 동작 및 둘 사이의 차이점
  3. 새로운 타입으로 만든 객체가 값에 의해 전달되는 경우에 어떤 의미를 줄 것인가?
    복사 생성자
  4. 새로운 타입이 가질 수 있는 적법한 값에 대한 제약은 무엇으로 잡을 것인가?
    클래스의 불변속성(invariant) : 클래스의 데이터 멤버의 몇 가지 조합 값만은 반드시 유효해야 한다.
  5. 기존의 클래스 상속 계통망(inheritance graph)에 맞출 것인가?
  6. 어떤 종류의 타입 변환을 허용할 것인가?
    암시적(implicitly), 명시적(explicit) 변환 구현
  7. 어떤 연산자와 함수를 두어야 의미가 있을까?
  8. 표준 함수들 중 어떤 것을 허용하지 말 것인가?
    private 함수
  9. 새로운 타입의 멤버에 대한 접근권한을 어느 쪽에 줄 것인가?
  10. 선언되지 않은 인터페이스로 무엇을 둘 것인가?
    타입이 제공할 보장의 종류
  11. 새로 만드는 타입이 얼마나 일반적인가?
    템플릿에 대한 고찰
  12. 정말로 꼭 필요한 타입인가?

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


  1. 대체적으로 효율적일 뿐만 아니라 복사손실 문제까지 막아 줍니다.
    복사손실 문제(slicing problem) : 파생 클래스 객체가 기본 클래스 객체로서 전달되는 경우 기본 클래스의 복사 생성자가 호출되고 파생 클래스 객체로 동작하게 해 주는 특징들이 ‘싹둑 잘려’ 떨어지고 맙니다.
  2. 값에 의한 전달이 괜찮은 유일한 타입은 기본제공 타입, STL 반복자, 함수 객체타입 뿐입니다.

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


  • 지역 스택 객체에 대한 포인터나 참조자를 반환하는 일, 혹은 힙에 할당된 객체에 대한 참조자를 반환하는 일, 또는 지역 정적 객체에 대한 포인터나 참조자를 반환하는 일은 그런 객체가 두 개 이상 필요해질 가능성이 있다는 절대로 하지 마세요.
1
2
3
Rational w, x, y, z;
...
w = x * y * z; // operator*(operator*(x,y), z)
  1. 지역 스택 객체에 대한 포인터나 참조자
    함수가 끝날때 객체가 소멸되기에 잘못된 메모리를 가르키고 있는 참조자/포인터입니다.
  2. 힙에 할당된 객체에 대한 참조자를 반환하는 일
    위와 같이 연산이 한문장에서 두번 이뤄질 때 메모리 해재에 대한 보장이 이뤄지지 못합니다.
  3. 지역 정적 객체에 대한 포인터나 참조자를 반화하는 일
    static을 사용하여 다중 쓰레드 환경에서 보장되지 못할 수 있고 단적인 예로 if((a*b) == (c*d))의 값이 항상 참이 나오는 오류를 범할 수 있습니다.