모든 자원 관련 코드를 RAII 객체로 관리한다면, 사실 자원에 대한 직접적인 참조를 수행할 경우는 없다고 할 수 있다.
그렇지만 이미 현장에서 쓰이는 많은 API 들이 직접적으로 자원을 참조하는 형식으로 만들어져 있다.
즉, RAII 객체를 사용한다 하더라도 실제 현장에서는 자원을 직접 참조해야하는 경우가 허다하는 것이다.
아래의 예시 코드를 보자.
// Factory Function
std::tr1::shared_ptr<Investment> pInv(createInvestment()); // 항목 13에서 사용된 팩토리 함수
...
int daysHeld(const Investment *pi); // 실제로 제공되는 현장의 어떤 API
...
int days = daysHeld(pInv); // 에러!
위 코드에서 에러가 나는 이유는 무엇일까?
사용하려는 함수에서 요구하는 인자는 Investment * 타입이지만, 실제로 넘기는 인자는 tr1::shared_ptr<Investment> 타입이기 때문이다.
즉, daysHeld 함수에서 요구하는 것은 pInv 에서 관리하는 실제 자원 객체인 것이다.
이러한 경우에는 pInv 객체에서 관리하는 자원으로 변환할 방법이 필요해지는데, 암시적 및 명시적 변환이 존재한다.
일반적으로 대부분의 스마트 포인터 객체는 get() 이라는 이름의 명시적 변환을 수행하는 멤버 함수를 제공한다.
해당 함수는 각 스마트 포인터 내부의 실제 자원에 대한 포인터 사본을 반환한다.
// get
int days = daysHeld(pInv.get()); // 이제 문제 없음
이러한 방식이 아니라도 대부분 역참조 연산자 (operator → , operator *) 를 오버로딩하고 있기 때문에, 아래의 예시처럼 암시적 변환도 쉽게 가능하다.
class Investment { // 여러 형태의 투자를 모델링한 클래스 계통의 최상위 클래스
public:
bool isTaxFree() const;
...
};
Investment* createInvestment(); // 팩토리 함수.
std::tr1::shared_ptr<Investment> pi1(createInvestment()); // tr1::shared_ptr이 자원관리를 맡도록 합니다.
bool taxable1 = !(pi1->isTaxFree()); // operator->를 써서 자원에 접근합니다.
...
std::tr1::shared_ptr<Investment> pi2(pInv1); // auto_ptr이 자원관리를 맡도록 합니다.
bool taxable2 = !((*pi2).isTaxFree()); // operator*를 써서 자원에 접근합니다.
명시적 변환 방식이 보다 더 불편하지만 안전하다.
반면에 암시적 변환 방식은 보다 더 편리하지만 위험하다.
// 암시적 변환의 좋은 예시
void changeFontSize(FontHandle f, int newSize); // 폰트 API의 일부
Font f(getFont());
int newFontSize;
...
changeFontSize(f, newFontSize); // Font에서 FontHandle로 암시적 변환을 수행합니다.
//////////////////
// 암시적 변환의 나쁜 예시
Font f1(getFont());
...
FontHandle f2 = f1; // 원래 의도는 Font 객체를 복사하는 것이었는데, 엉뚱하게도 f1이 FontHandle로 바뀌고 나서 복사되어 버렸습니다.
상황에 따라서 암시적 변환이 편리하게 사용될 수도 있겠지만, 거의 대부분의 경우에는 get 함수처럼 명시적 방식을 선택하는 것이 낫다.
당장 위와 같은 예시에서 이 정도의 편의성 차이라면 보다 안정적인 방식이 훨씬 합리적이기 때문이다.
지금까지의 예시에서 보다시피 RAII 객체는 자기 자신이 관리하는 자원에 대한 접근을 명시적, 암시적으로 허용하는 형태가 많다.
즉, 캡슐화가 제대로 이루어지지 않는 객체라는 말인데, RAII 객체는 데이터 은닉을 목적으로 사용하는 객체가 아니기 때문에 크게 문제는 없다.
캡슐화를 위배하든, 데이터 은닉성이 떨어지든간에 자원 획득 시 초기화 기능이나 자원 해제 같은 기능만 제대로 동작하면 된다는 의미이다.
항목 17) new 생성 객체를 스마트 포인터로 저장하는 코드에 대한 별도 관리 (0) | 2021.12.05 |
---|---|
항목 16) new 및 delete 사용할 때는 반드시 형태를 맞추자 (0) | 2021.12.05 |
항목 14) 자원 관리 클래스의 복사 동작에 대해서 (0) | 2021.12.05 |
항목 13) 객체를 활용한 자원 관리 ( RAII 패턴 ) (0) | 2021.12.05 |
항목 12) 객체의 모든 부분을 빠짐없이 복사하자 (0) | 2021.12.05 |
댓글 영역