- 실제 자원을 직접 접근해야 하는 기존 API들도 많기 때문에, RAII 클래스를 만들 때는 그 클래스가 관리하는 자원을 얻을 수 있는 방법을 제공해야 한다.
- 자원 접근은 명시적 변환 혹은 암시적 변환을 통해 가능하다. 안전성만 따지면 명시적 변환이 대체로 낫지만, 편의성을 보면 암시적 변환이 더 낫다.
자원 관리 클래스에서의 자원 접근 허용
자원 관리 클래스는 자원 누출을 막을 수 있는 보호막 역할을 한다.
하지만, 자원에 대한 직접적인 접근이 필요한 상황이 있다.
예를 들어, 스마트 포인터를 이용하여 자원 관리를 하는 경우 원래의 자원을 매개변수로 사용해야 하는 함수가 있을 때 스마트 포인터를 넘긴다면 에러가 발생한다.
std::tr1::shared_ptr<Investment> pInv(createInvestment());
int dayHeld(const Investment *pi);
int days = daysHeld(pInv); // 에러
함수에서 Investment* 타입의 실제 포인터를 원하는데 shared_ptr<Investment>를 넘기기 때문이다.
이런 상황이 종종 있기 때문에 RAII 클래스를 실제 자원을 변환할 방법이 필요하다.
이런 목적에 일반적인 방법을 쓴다면 두 가지가 있다. 명시적 변환과 암시적 변환이다.
tr1::shread_ptr 및 auto_ptr은 명시적 변환을 수행하는 get이라는 멤버 함수를 제공한다.
이 함수를 사용하면 각 타입으로 만든 스마트 포인터 객체에 들어있는 실제 포인터(사본)를 얻을 수 있다.
int days = daysHeld(pInv.get());
tr1::shared_ptr과 auto_ptr은 포인터 역참조 연산자(operator->, operator*)도 오버로딩하고 있다.
따라서 자신이 관리하는 실제 포인터에 대한 암시적 변환도 쉽게 할 수 있다.
class Investment {
public:
bool isTaxFree() const;
...
};
Investment* crateInvestment();
std::tr1::shared_ptr<Investment> pi1(createInvestment());
bool taxable1 = !(pi1->isTaxFree());
...
std::auto_ptr<Investment> pi2(createInvestment());
bool taxable2 = !((*pi2).isTaxFree());
...
RAII 객체를 만들 때도 암시적 변환 함수를 제공해 주는 것이 좋다.
FontHandle getFont(); // C API
void releaseFont(FontHandle fh); // C API
class Font {
public:
explicit Font(FontHandle fh) : f(fh) {} // 자원 획득(값에 의한 전달)
~Font() { releaseFont(f); }
private:
FontHandle f; // 실제 폰트 자원
};
C API는 FontHandle을 사용하도록 만들어져 있으며 규모도 크다고 가정하면, Font 객체를 FontHandle로 변환해야 할 경우도 적지 않을 것이다. 이를 위해 명시적 변환 함수로 get을 제공할 수 있을 것이다.
class Font {
public:
...
FontHandle get() const { return f; } // 명시적 변환 함수
...
};
이렇게 구현한다면 하부 수준 API를 쓰고 싶을 때마다 get을 호출해야 한다.
void changeFontSize(FontHandle f, int newSize); // 폰트 API 이부
Font f(getFont());
int newFontSize;
...
changeFontSize(f.get(), newFontSize); // Font에서 FontHandle로 명시적으로 바꾼 후 넘김
하지만, 이렇게 구현한다면 변환할 때마다 함수를 호출해야 한다.
이를 해결하기 위해서는 암시적 변환 함수를 제공하면 된다.
class Font {
public:
...
operator FontHandle() const { return f; }
...
};
Font f(getFont());
int newFontSize;
...
changeFontSize(f, newFontSize); // 암시적 변환
하지만, 암시적 변환이 들어가면 실수할 수 있는 가능성이 커진다.
Font f1(getFont());
...
FontHandle f2 = f1; // f1이 FontHandle로 바뀌고 복사됨
이렇게 되면 f1이 관리하고 있던 폰트가 f2를 통해서도 직접 사용할 수 있는 상태가 된다.
RAII 클래스를 실제 자원으로 바꾸는 방법으로서 명시적 변환을 제공할 것인지 암시적 변환을 허용할 것인지에 대한 결정은 사용환경에 따라 적절히 결정해야 한다.
보통의 경우 "맞게 쓰기에는 쉽게, 틀리게 쓰기에는 어렵게" 만들어져야 하기 때문에, 암시적 변환보다는 get 등의 명시적 변환 함수를 제공하는 쪽이 나을 것이다. 원하지 않은 타입 변환이 일어날 여지를 줄여주는 것이 좋기 때문이다.
RAII 클래스에서 자원 접근 함수를 열어 주는 설계가 캡슐화에 위배되는 것일 수는 있지만 완전히 틀린 설계가 아니다.
RAII 클래스는 데이터 은닉이 목적이 아니라 자원 해제를 실수 없이 이루어지도록 하는것이 목적이기 때문이다.
RAII 클래스 중에는 자원의 엄격한 캡슐화와 느슨한 캡슐화를 동시에 지원하는 것들도 꽤 있다.
tr1::shared_ptr이 대표적인 예인데, 이 클래스는 참조 카운팅에 필요한 장치들은 모두 캡슐화하고 있지만, 관리하는 포인터를 쉽게 접근할 수 있는 방법을 제공하고 있다.