본문 바로가기
Book & Lecture/Effective C++

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

by Dev Diary Hub 2023. 3. 12.
반응형

Effective C++ 항목18 : 

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

 

클래스를 설계할 때, 함수 인자가 비슷하면 실수할 우려가 있다!

class Date
{
  public:
    Date(int month, int day, int year);
};

// month 와 day 를 거꾸로 적음. int형이기 때문에 정상적으로 프로그램이 진행된다..
Date d1(12, 3, 2023);
// day를 31을 초과한 값으로 넣어줌. int형이므로 당연히 에러는 나지 않는다..
Date d2(3, 91, 2023);

 

위의 Date 클래스의 경우, 월과 일을 반대로 적거나 허용할 수 없는 값이 인자로 들어갈 수 있다.

심지어 컴파일 에러도 발생하지 않는다!

 

이러한 실수를 방지하려면 연,월,일을 클래스로 다시 정의할 수도 있고,

// 실수를 사전에 막아보자
class Date
{
  Date(const Month& m, const Day& d, const Year& y);
  ...
};

// 타입 에러 발생 (연,월,일을 객체 생성해서 주입해야하므로!)
Date d1(30, 3, 1995);

// 컴파일 에러.. (타입 에러)
Date d2(Day(30), Month(3), Year(1995));

// 컴파일 성공!
Date d3(Month(3), Day(30), Year(1995));

 

여기서 타입 안정성을 신경쓰면, 월(month)을 아래와 같이 비지역 정적 객체를 이용할 수 있다.

class Month
{
  public:
    // 1~12월의 Month를 반환
    // 비지역 정적 객체이기 때문에 함수를 통해 Month 객체를 반환해야한다.
    // (초기화 순서가 개별 번역 단위에서 정해지므로!)
    static Month Jan() { return Month(1); }
    static Month Feb() { return Month(2); }
    ...
    static Month Dec() { return Month(12); }
    ...
  private:
    // Month 객체는 하나만 존재해야하므로 private 접근 지정
    explicit Month(int m);
};

 

객체에서 포인터를 생성하는 경우, shared_ptr로 선언하는 것이 좋다!

일반 포인터로 선언할 경우, 나중에 delete할 타이밍을 잡기 어려울 수도, delete하는 것 자체를 잊어버릴 수도 있다.

따라서 처음부터 shared_ptr로 선언하는 것이 좋다.

 

또한, share_ptr로 선언된 포인터는 교차 DLL 문제를 막아주는 장점도 있다. 객체 생성시에 A.dll의 new를 사용하여 포인터를 생성했다면 삭제할 때도 A.dll의 delete를 사용해야한다. 일반 포인터를 사용하면 A.dll의 new를 사용했다가 B.dll의 delete를 사용하는 문제가 일어날 수도 있다. 이것이 교차 DLL 문제이다. share_ptr은 이러한 문제를 사전에 예방하도록 동작한다.

 

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