상세 컨텐츠

본문 제목

항목 15) 자원 관리 클래스에서 관리되는 자원은 외부 접근을 차단하자

C++/Effective C++

by DeaKyungLee 2021. 12. 5. 14:34

본문

자원에 대한 직접적인 접근

 

모든 자원 관련 코드를 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 객체는 자기 자신이 관리하는 자원에 대한 접근을 명시적, 암시적으로 허용하는 형태가 많다.

즉, 캡슐화가 제대로 이루어지지 않는 객체라는 말인데, RAII 객체는 데이터 은닉을 목적으로 사용하는 객체가 아니기 때문에 크게 문제는 없다.

캡슐화를 위배하든, 데이터 은닉성이 떨어지든간에 자원 획득 시 초기화 기능이나 자원 해제 같은 기능만 제대로 동작하면 된다는 의미이다.

 

Things to Remember

  • 실제 자원을 직접 접근해야 하는 기존 API들도 많기 때문에, RAII 클래스를 만들 때는 그 클래스가 관리하는 자원을 얻을 수 있는 방법을 열어 주어야 한다.
  • 자원 접근은 명시적(get), 암시적( operator() ) 변환을 통해서 가능하다. 명시적 방식이 불편하지만 더 안전하고, 암시적 방식이 더 편리하지만 위험하다.

관련글 더보기

댓글 영역