인터페이스를 사용하는 사용자의 실수를 설계 단계에서 미리 방지하자.
→ 좋은 인터페이스 설계
class Date{
public:
Date(int month, int day, int year);
};
위의 클래스는 문법적으로 아무런 문제가 없다.
하지만 사용하는 입장에서 실수 하기 딱 좋은 코드이기도 하다.
...
Date d(30, 3, 1995); // 3,30을 넣었어야 함.
Date d(3, 40, 1995); // 40은 말이 안됨.
위와 같은 상황을 방지하는 간단한 방법은 Wrapper 타입을 이용하는 것이다.
struct Day{ explicit Day(int d) :val(d){} int val; };
struct Month{ explicit Month(int m) :val(m){} int val; };
struct Year{ explicit Year(int y) :val(y){} int val; };
class Date{
public:
Date(const Month& m, const Day& d, const Year& y);
};
Date d(30, 3, 1995); // 타입이 틀렸습니다!
Date d(3, 40, 1995); // 타입이 틀렸습니다.
Date d(Month(3), Day(30), Year(1995)); // 타입이 맞았습니다!
다시 말하자면, 각 인자에 대한 구조체를 따로 정의해서 사용하자는 것이다.
그리고 해당 구조체 내부에서 따로 값 검사를 진행하는 것도 괜찮다.
struct Month{
explicit Month(int m) :
{
if ( 0 < m && 13 > m) // 0월, 13월은 존재할 수 없다
{
val = m;
}
}
int val;
};
...
좋은 인터페이스 설계의 또 다른 예시를 보자
Investment* createInvestment(); // (1) 설계자
std::tr1::shared_ptr<Investment> pInv(createInvestment()); // (2) 사용자 (설계자가 예상한 사용 방식)
위의 팩토리 함수의 설계자는 사용자가 스마트 포인터를 사용하리라 생각했다.
하지만 (2) 와 같은 방식의 코드를 사용자가 깜빡했다면?
포인터 삭제를 깜빡하거나, 똑같은 포인터를 두 번 삭제하는 등의 연쇄적인 문제가 발생 할 수 있다.
그렇다면 설계 자체에서 스마트 포인터를 반환하면 어떨까?
std::tr1::shared_ptr<investment> createInvestment();
스마트 포인터 사용을 깜빡해서 발생하는 문제는 발생하지 않을 것이다.
또 다른 가정으로,
만약 해당 객체에 대한 삭제 함수를 따로 제공한다면 어떨까?
즉, 스마트 포인터로 감싸진 객체에 대한 삭제 함수를 설계자가 미리 만들어 두는 것이다.
getRidOfInvestment()
{
...
...
delete ...
}
여러가지 문제를 미리 예방하고 더 깔끔한 형식처럼 보이지만, 이것만으로는 오히려 사용자의 실수를 더 유발할 수 있다.
만약 이런 상황처럼 사용자가 반드시 해당 함수를 사용해서 객체를 삭제하도록 하고 싶다면, shared ptr의 또 다른 기능을 활용하면 된다.
shared_ptr 생성시에 두 번째 인자로 삭제자를 받는다.
즉, 해당 자원에 대한 카운트가 0이 되어서 삭제되는 시점에 호출될 함수를 사용자가 정의할 수 있다는 의미이다.
이것을 정리하자면 createInvestment 함수는 아래와 같이 변경되는 것이 바람직하다.
std::tr1::shared_ptr<investment> createInvestment()
{
// 0 부분에 포인터가 들어가야 하기 때문에 static_cast로 캐스팅
std::tr1::shared_ptr<investment>
retVal(static_cast<investment*>(0), getRidOfInvestment); // 0 이 아니라 실제 객체를 통해 바로 초기화하는 것이 바람직하다.
retVal = ...; // 이유는 항목 26 에서 다룬다.
return retVal;
}
shared_ptr 의 또 다른 장점은 서로 다른 DLL 내부에서도 자신이 가리키는 객체에 대한 고유성을 자동으로 지킨다는 것이다.
즉, shared_ptr 을 통해 관리되는 자원은 서로 다른 DLL 에서 같은 이름의 자원이 존재하더라도
자원 삭제 시점에는 선언 시점에 할당한 자원을 제대로 삭제한다는 말이다.
항목 20) 값 전달 보다는 상수객체 참조자 전달 방식을 고려하자. (0) | 2022.08.27 |
---|---|
항목 19) 클래스 설계는 타입 설계와 똑같이 취급하자 (0) | 2021.12.05 |
항목 17) new 생성 객체를 스마트 포인터로 저장하는 코드에 대한 별도 관리 (0) | 2021.12.05 |
항목 16) new 및 delete 사용할 때는 반드시 형태를 맞추자 (0) | 2021.12.05 |
항목 15) 자원 관리 클래스에서 관리되는 자원은 외부 접근을 차단하자 (0) | 2021.12.05 |
댓글 영역