자원 관리 클래스의 자원 접근
자원에 접근하려 하는 예
아래 createInvestment 로 자원을 만든 후, pInv 에 넘겨주는 경우를 생각해보자 (항목 13 예제)
shared_ptr<Investment> pInv(createInvestment());
그리고 Investment 객체를 사용하는 함수가 다음과 같이 정의 되어있다고 하자
int daysHeld(const Investment *p);
//그러면 이렇게 호출하고 싶겠죠
int days = daysHeld(pInv);
그러나 위와 같이 할 경우 컴파일이 되지 않는다.daysHeld
는 Investment *
타입의 포인터를 원하는데 , shared_ptr
타입을 넘기고 있으니 안되는 것!
RAII 클래스 객체의 자원 변환
위 문제를 해결하기 위해서는 결국 RAII 클래스 (예시에서는 shared_ptr
) 객체를
실제 자원 (Investment *
) 으로 변환할 방법이 필요하다.
일반적으로 변환을 위한 방법은 다음과 같다.
1) 명시적 변환 (explicit conversion)
2) 암시적 변환 (implicit conversion)
스마트 포인터의 명시적 변환
shared_ptr 같은 스마트 포인터는 명시적 변환을 수행하는 get
이라는 멤버 함수을 제공한다.get
함수를 사용하면 스마트 포인터 객체에 들어있는 실제 포인터(의 사본)를 얻어 낼 수 있다.
int days = daysHeld(pInv.get()); //pInv 에 있는 실제 포인터를 넘겼다.
스마트 포인터의 암시적 변환
스마트 포인터는 역참조 연산자 operator ->
및 operator *
도 오버로딩 하고 있다.
위 연산자를 통해 관리하고 있는 실제 포인터에 대한 암시적 변환을 쉽게 할 수 있다.
class Investment {
public:
bool isTaxFree() const;
}
shared_ptr<Investment> pInv(createInvestment());
bool tax_label1 = !(pInv->isTaxFree()); //operator-> 를 써서 자원에 접근한다.
bool tax_label2 = !((*pInv).isTaxFree());
자원 접근을 제공하는 함수
그렇다면 실제 자원 접근을 제공하는 함수를 만들게 된다면 어떤 점을 주의해야 할까
C API 로 직접 조작이 가능한 폰트를 RAII 클래스로 둘러싸서 쓰는 경우를 생각해 보자.
FontHandle getFont(); // C API 함수
void releaseFont(FontHandle f); // C API
class Font {
public:
explict Font(FontHandle fh) // 자원 획득
: f(fh) {}
~Font() { releaseFont(f) };
private :
FontHandle f;
}
명시적 변환
Font 객체를 FontHandle 로 변환해주는 get 함수를 제공한다 하자.
class Font {
....
public:
FontHandle get() const { return f; } //명시적 변환 함수
}
이렇게 하면 사용시에,
void ChangeFontSize(FontHandle f, int newSize);
Font f(getFont());
int newFontSize;
...
ChangeFontSize(f.get(), newFontSize); //Font 에서 FontHandle 로 명시적 변환 후에 넘긴다.
단점 : 변환 할때마다 get 함수를 호출해야 하는 번거로움이 발생한다.
암시적 변환
operator() 를 정의해서 암시적으로 Font 를 FontHandle 로 변환하는 함수를 제공한다.
class Font {
public:
operaotr FontHandle() const { return f }; // 암시적 변환 함수. 변환 연산자라고 불린다.
}
사용시에는
Font f1(getFont());
int newFontSize;
ChangeFontSize(f1,newFontSize);
변환 연산자
책에는 나와 있진 않지만,operaotr FontHandle() const
이런 형태의 함수를 변환 연산자라고 한다.
객체에서 일반 타입 값을 받을 수 있다.
FontHandle f = f1;
// 위 표현은 아래와 똑같다.
f = f1.operator();
암시적 변환의 문제
그러나,진짜 Font 를 복사하려고 한 부분에서 FontHandle 로 바뀔 수 있는 문제가 발생한다.
Font f1(getFont());
FontHandle f2= f1; // Font 객체를 복사하려고 했는데, FontHandle 로 바뀌고 나서 복사 되었다.
// 근데 그냥 Font 를 안바꾸고 FontHandle 에 복사할 수 있나....
또한 이렇게 하면 f1 이 관리하고 있는 FontHandle 를 f2 에서도 직접 사용할 수 있는 형태가 된다.
f1에서 FontHandle 을 해제하면, f2 는 해제된 자원을 갖게 된다.
(이 문제는 암시적 변환도 마찬가지 아닌가)
결론
RAII 클래스를 실제 자원으로 바꾸는 방법으로서 명시적 변환 혹은 암시적 변환을 제공할 것인지에 관한 것은
해당 RAII 클래스만의 특정한 용도와 환경에 따라 달라진다.
자원 접근 함수를 제공하는 것은 캡슐화에 위배되는 것은 아닌가?
그렇기 하지만 틀려먹은 설계는 아니라고 저자는 말한다.
RAII 클래스의 목적이 애초부터 데이터 은닉이 아니기 때문이다.
자원 해제 가 실수 없이 이뤄지기만 하면 되기 때문에 , 캡슐화가 꼭 필요한 것은 아니다.
스마트 포인터 역시 관리하는 포인터를 접근할 수 있는 통로는 열어둔다.
요약
- 실제 자원을 접근해야 하는 기존 API 들이 많기 때문에, RAII 클래스를 만들 때는 그 클래스가 관리하는 자원을 얻는 방법을 제공해 주어야 한다.
- 자원 접근은 명시적 변환 혹은 암시적 변환을 통해 가능하다. 안전성만 따지면 명시적 변환이 대체적으로 더 낫다.
그러나 편의성은 암시적 변환이 괜찮다.
'C++ > Effective C++' 카테고리의 다른 글
항목 17: new 로 생성한 객체를 스마트 포인터에 저장하는 코드는 별도의 한 문장으로 만들자 (0) | 2020.09.25 |
---|---|
항목 16: new 및 delete 를 사용할 때는 형태를 반드시 맞추자 (0) | 2020.09.24 |
항목 14: 자원 관리 클래스의 복사 동작에 대해 진지하게 고찰하자 (0) | 2020.09.23 |
항목 13: 자원 관리에는 객체가 그만! (0) | 2020.09.23 |
항목 31 : 파일사이의 컴파일 의존성을 줄이자 (0) | 2020.09.22 |