항목 15 자원 관리 클래스에서 관리되는 자원은 외부에서 접근할 수 있도록 하자
우리가 auto_ptr이나 shared_ptr을 보면 이 스마트 포인터가 관리하는 자원에 외부에서 접근 할 수 있도록 설계 되어있다.
std::shared_ptr<Test> test(new Test());
test.get(); // Test* 를 반환
test->memberFunction(); // ->로 자원에 접근하여 멤버 함수 호출 가능
(*test).memberFuncion(); // *로 자원에 접근한 후 멤버 함수 호출 가능
자, 그렇다면 우리가 항목 14에서 배운대로 직접 자원관리 클래스를 만들때에도 이러한 점을 고려하는 것이 좋다.
예를 들어 C API에서 글자를 출력하기 위해 제공하는 FontHandle이라는 자원을 관리하는 Font클래스를 직접 만든다고 생각해 보자.
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;
};
자 이런식으로 FontHandle을 관리해주는 Font클래스를 직접 만들었다. 그리고 우리는 여기에 외부에서 Font클래스가 관리하는 자원에 접근하기 위해 다음과 같이 명시적 반환 함수를 만들 수 있다.
class Font {
public:
...
FontHandle get() const { return f; } // 명시적 반환 함수
...
};
그리고 C API에서 폰트의 크기를 변경하는 함수를 다음과 같이 제공한다고 했을때 우리는 이렇게 사용할 수 있다.
void changeFontSize(FontHandle f, int newSize); // C API에서 제공
Font f(getFont());
int newFontSize;
...
changeFontSize(f.get(), newFontSize); // 명시적으로 Font에서 FontHandle로 바꾼후 넘김
그런데 여기서 .get()을 호출하는것이 마냥 귀찮은 사람도 있을 수 있다. 그래서 우리는 Font에서 FontHandle로 암시적 변환 함수를 제공하도록 할 수 있다.
class Font {
public:
...
operator FontHandle() const // 암시적 변환 함수
{ return f; }
...
};
이런식으로 Font클래스 내에서 FontHandle의 생성자 operator?를 오버로딩 하면 다음과 같이 동작 할 수 있다.
Font f(getFont());
int newFontSize;
...
changeFontSize(f, newFontSize); // 암시적으로 Font에서 FontHandle로 바꾼후 넘김
사실 나는 이 코드가 정말로 동작한다는 것을 보고 감탄을 했었다. 굉장히 신박해 보였기 때문이다. 하지만 이 방법은 그리 안전하지 않다.
Font f1(getFont());
...
FontHandle f2 = f1;
여기서 사용자의 의도는 Font객체를 복사하려는 것이었는데 실수로 FontHandle을 써버린것이다. 그러면 여기서 f1은 암시적으로 FontHandle로 변환이 되어 f2와 자원을 공유해버리게 된다. 그리고 여기서 만약 f1이 소멸되어 버린다면 그 안에 있는 자원도 마찬가지로 소멸시켜 버리기 때문에 f2는 이미 소멸되어 버린 FontHandle을 가진 이상한 상태가 되어 버린다. 그리고 저것이 사용자의 실수가 아니라 만약 의도적으로 FontHandle을 복사하려고 저런 코드를 짰다면 애초부터 저런 위험이 있는 코드를 짜지 못하도록 막아 두었어야 한다. 그러므로 위와 같은 방법은 사용자가 사용하기에 편하도록 도와주긴 하지만 틀리게 사용하기도 쉽기 때문에 그리 권장하지는 않는다. 하지만 이 방법도 쓰이는 곳이 종종 있기 때문에 마냥 좋지 않은 방법으로 규정하지는 말자.