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

우리가 만든 인터페이스를 잘못 사용했을 경우에 우리의 인터페이스가 최소한 항의의 몸부림이라도 해야 한다.

이상적으로는, 어떤 인터페이스를 어떻게 써 봤는데 결과 코드가 사용자가 생각한 대로 동작하지 않으면, 그 코드는 컴파일되지 않아야 한다.

1. 아래 코드의 문제점은?
class Date {
public:
    Date(int month, int day, int year);
    ...
};

아래와 같이 잘 못 사용할 가능성이 존재한다.

Date d(18, 12, 2017); // 원래 2017년 12월 18일을 쓸려고 했는데..
Date d(12, 40, 2017); // 원래 12월 30일을 쓸려고 했는데, 오타
2. 새로운 타입을 도입하여 인터페이스를 강화해 보자
struct Day {
    explicit Day(int d)
    : val(d) {}
    int val;
};

struct Month {
    explicit Month(int d)
    : val(d) {}
    int val;
};

struct Year {
    explicit Year(int d)
    : val(d) {}
    int val;
};

class Date {
public:
    Date(const Month& m, const Day& d, const Year &y);
    ...
};

Date d(18, 12, 2017);   // 에러! 타입이 틀림

Date d(Day(18), Month(12), Year(2017));   // 에러 타입이 틀림

Date d(Month(12), Day(18), Year(2017));   // OK!
3. Month, Day, Year 클래스들이 갖는 값이 범위를 넘어 갈 수도 있다. 해결책은?

enum을 사용해서 할 수도 있지만 enum은 타입변환이 일어 날 수 있어서 이 경우 안전하지 않을 수 있다.

대신 Month 같은 경우 유효한 값을 미리 정의해서 사용할 수 있다.

class Month {
public:
    static Month Jan() ( return Month(1); }
    static Month Feb() ( return Month(2); }
    ...
    static Month Dec() ( return Month(12); }
private:
    explicit Month(int m); // Month 값이 새로 생성되지 않게함.
    ...
};

Date d(Month::Dec(), Day(18), Year(2017));

특정한 월을 나타내는 데 객체를 쓰지 않고 함수를 쓴 것은 비지역 정적객체의 초기화 순서는 정해져 있지 않기 때문입니다.(Item 4 참조)

4. 어떤 타입에 제약을 부여하여 그 타입이 할 수 있는 일을 제한하는 방식의 예를 들라.
if (a*b = c) // == 를 하려고 했는데.

위와 같은 코드를 방지하려면 operaotr* 함수가 const를 반환하도록 한다.

5. 그렇게 하지 않을 번듯한 이유가 없다면 사용자 정의 타입은 기본제공 타입처럼 동작하게 만들어야 한다.
6. 아래 코드를 고치면?
Investment* createInvestment();

std::shared_ptr<Investment> createInvestment();

7. 포인터를 직접 삭제하지 않고 getRidOfInvestment() 함수와 같은 것을 사용하면 더 좋을까?

그렇지 않다. 이 함수를 사용하지 않고 delete를 사용한다거나, 이 함수 호출을 깜박할 수도 있다.

  1. 교차 DLL(cross DLL) 문제란?

특정 DLL에서 힙에 만들어진 객체를 다른 DLL에서 delete 할 때 런타임 에러가 발생하는 문제이다.

하지만 shared_ptr을 쓰면 레퍼런스 카운트가 0이 될 때 new를 호출하여 생성한 DLL의 delete를 호출해 준다. 그래서 교차 DLL 문제를 해결할 수 있다.

  1. 부스트의 shared_ptr은 원시 포인터의 크기의 두배이다. 또한 내부적으로 사용하는 데이터등으로 인해 실제 데이터는 훨신 많다. 다중 쓰레드로 돌아가면 동기화 오버헤드도 있다. 물론 다중 쓰레드 지원을 비활성화 할 수도 있다. 선행 처리자 기호를 정의하면 된다.

정리

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

results matching ""

    No results matching ""