[Effective C++] Chapter 04. 설계 및 선언 | 항목 18~25

seunghyun·2024년 4월 14일
0

Effective C++

목록 보기
4/4

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

궁금증 질문:

(139쪽 참고)
“C++에서는 발에 치이고 손에 잡히는 것이 인터페이스입니다. 함수도 인터페이스요, 클래스도 인터페이스요, 템플릿 또한 인터페이스입니다. 인터페이스는 사용자가 여러분의 코드와 만리장성을 쌓는 접선수단입니다. …

C++은 C#처럼 별도의 인터페이스라는 것이 없으므로 위에서 설명하는 인터페이스는 어떠한 ‘구조’를 통칭하는 말일까요?


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

Q. 다음은 클래스 설계에 관련한 명제들입니다. 이 중 거짓인 명제 한 개를 골라주세요 (객관식)

  1. 어떤 타입에 대해 ‘값에 의한 전달’을 구현하는 쪽은 복사 생성자이다.
  2. T1 타입의 객체를 T2 타입의 객체로 암시적으로 변환되도록 만들고 싶다면, T1 클래스에 타입 변환 함수를 하나 만드는 방법이 있다. (이를테면 operator T2)
  3. 기존의 클래스의 기능 몇 개가 아쉽더라도, 파생 클래스를 새로 만드는 것이 권장된다.
    • 1번: 참
      • 147쪽 참고: 새로운 타입으로 만든 객체가 값에 의해 전달되는 경우를 고려하기
    • 2번: 참
      • 148쪽 참고: 어떤 종류의 타입 변환을 허용할 것인지 고려하기
    • 3번: 거짓
      • 149쪽 참고: 차라리 간단하게 비멤버 함수나, 템플릿을 몇 개 더 정의하는 편이 낫다고 합니다!!

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

대체적으로 효율적일 뿐만 아니라 복사손실 문제까지 막아 준다.
이번 항목에서 다룬 법칙은 기본제공 타입 및 STL 반복자, 그리고 함수 객체 타입에는 맞지 않는다. 이들에 대해서는 '값에 의한 전달'이 더 적절하다.

Q. ‘값에 의한 전달’이 저비용이라고 가정해도 괜찮은 유일한 타입 3가지는 무엇이 있을까요?

  • 답 참고 154쪽
    1. 기본 제공 타입

    2. STL 반복자

    3. 함수 객체 타입

      '값에 의한 전달'보다는 '상수객체 참조자에 의한 전달' 방식을 택하는 편이 대개 낫습니다

      대체적으로 효율적일 뿐만 아니라 복사손실 문제까지 막아줍니다.

      이번 항목에서 다룬 법칙은 기본제공 타입 및 STL 반복자, 그리고 함수 객체 타입에는 맞지 않으며, 이들에 대해서는 '값에 의한 전달'이 더 적절하다고 합니다.


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

참조자는 그냥 이름이다. 반드시 이미 존재하는 객체에 붙는 다른 이름이다.

[이것만은 잊지 말자]

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

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

데이터 멤버를 함수 인터페이스 뒤에 감추게 되면 구현상의 융통성을 가질 수 있다. 그리고 책에서 인상 깊었던 말이 있는데 한번 인용해보겠다.
public이란 '캡슐화되지 않았다'는 뜻이며, 실질적인 측면에서 이는 곧' 바꿀 수 없다'라는 의미를 담고 있다.
즉, 데이터 멤버를 public으로 만들 게 되면 나중에 데이터 멤버를 삭제하거나 변수이름을 변경하거나 타입을 변경하는 등의 작업이 힘들어지게 된다. 만약 라이브러리나 프레임워크 개발이라면 불가능하다고 봐야한다. 따라서 데이터 멤버는 private로 최대한 캡슐화하고 인터페이스 함수를 제공하는게 옳다고 할 수 있다.

[이것만은 잊지 말자]

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

23) 멤버 함수보다는 비멤버 비프렌드 함수와 더 가까워지자.

일반적으로 직관적으로 생각했을 때, 클래스와 연관된 함수들은 클래스의 멤버 함수로 두어야할 것 같다. 하지만 실질적으로 만약 비멤버 비프렌드 함수로 둘 수 있는 경우에는 비멤버 비프렌드 함수로 두는 것이 캡슐화 정도가 높아지고 패키징 유연성이 커진다. 왜냐하면 비멤버 비프렌드 함수는 멤버함수보다 접근권한이 약하고(public만 접근할 수 있음) 해당 클래스와 해당 함수를 파일분할(컴파일 의존성을 줄여준다. 컴파일 시간 감소!) 할 수 있기 때문이다.

class Example {
public:
    void doA() {}
    void doB() {}
    void doC() {}
    void doAll() {  // Bad!
        doA();
        doB();
        doC();
    }
};

void doAll(Example &ex) {  // Good!
    ex.doA();
    ex.doB();
    ex.doC();
}

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

암시적인 형변환을 지원하는 클래스가 있다고 하자. 이 클래스에 대해서 operator을 멤버함수로서 overload했다고 하자. 이경우에는 A B라고 했을 때 A위치에 대해서는 암시적 형변환이 지원되지 않는다. 따라서 이런 경우에는 operator*을 비멤버함수로서 overload해야한다.

[이것만은 잊지 말자]

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

25) 예외를 던지지 않는 swap에 대한 지원도 생각해 보자.

이 항목은 template에 대한 내용이 많이 등장함으로 이에 대해서 익숙하지 않으신 분들은 책을 읽으면서 헤매셨을수 도 있습니다. 관련 template내용에 대한 보충자료로서 http://egloos.zum.com/sweeper/v/2998778 를 추천합니다. 설명이 깔끔하고 명확하게 잘 되어있습니다. (강추!)

[std namespace에 대한 템플릿 제한사항]

  • std namespace에 대해서 프로그래머가 직접 만든 타입에 대해 표준 템플릿을 완전 특수화하는 것은 허용된다.
  • std namespace에 대해 프로그래머가 새로운 템플릿을 정의를 하는 것은 금지된다. (따라서 template overload도 못함.) (컴파일/실행 됨. 근데 실행결과가 미.정.의.사.항...Damn?!)
  • 더 있을 수도 없을 수도... (궁금한 사람들은 검색해보세요)
namespace ExampleStuff {
    template <typename T>
    class ExampleImpl {
    };

    template <typename T>
    class Example {
    public:
        void swap(Example &ex) {
            using std::swap;
            swap(pImpl, ex.pImpl);
        }
    private:
        ExampleImpl<T> *pImpl;
    };

    template <typename T>
    void swap(Example<T> a, Example<T> b) {
        a.swap(b);
    }
}

template <typename T>
void func(T& a, T& b) {
    using std::swap;
    swap(a, b);  // find function by argument-dependent lookup.
}

클래스에 대한 swap을 마련할 생각이라면, 단순히 std::swap에 대한 완전 특수화 함수를 정의하면 된다. 하지만 클래스 템플릿에 대한 swap 특수화 버전을 마련할 때는 다른 방법을 써야한다. (함수 템플릿에 대한 부분 특수화는 불가능하기 때문에) 해결책은 위 코드와 같다. func함수에서 일어나는 일은 다음과 같다. 인자 기반 탐색에 의해 인자 타입과 동일한 namespace에서 swap의 특수화 버전을 먼저 탐색하게 되고, 없을 경우 using에 의해 std namespace에서 탐색하게 된다. 따라서 우리가 원하는 목적을 이룰 수 있다. 문제는 class를 사용하는 사용자가 swap을 사용할 때 func함수와 같이 사용할 것을 기대해야만 한다는 것이다. 사용자가 만약 std::swap()을 사용한다면 다 무용지물이 된다. 그래서 완벽한 방법은 아닌 것 같다... 더 좋은 해결책은 없을까??? (비야네 날 보고있다면 정답을 알려줘!)

[이것만은 잊지 말자]

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

0개의 댓글