자원 관리 클래스는 실수로 터질 수 있는 자원 누출을 튼튼히 막아 주는 보호벽 역할을 해준다.
그러나 이미 많이 사용하고 있는 API들이 직접 참조하도록 만들어져 있어서, 자원 관리 객체의 보호벽을 슬그머니 넘어가

실제 자원을 직접 조작해야 할 일이 있다.

 

13장에서 작성했던 Investment 예제를 사용해서 shared_ptr을 통해 Investment 객체를 가리킨다고 하자.

 

1
2
3
4
5
6
7
8
9
10
11
int dayHeld(const Investment *pi)
{
    ...
}
 
void func()
{
    std::tr1::shared_ptr<Investment> pInv(createInvesment());
 
    int days = dayHeld(pInv);    //컴파일 에러 발생.
}
cs

 

위와 같이 dayHeld라는 Investment* 타입의 매개변수를 가진 함수에 스마트 포인터 객체를 전달한다면 컴파일 에러가 발생한다.

이유는 dayHeld 함수는 Investment* 타입의 실제 포인터를 원하는데, tr1::shared_ptr<Investment> 타입의 객체를 넘겨주었기

때문이다. 

사정이 이렇다 보니, RAII 클래스(shared_ptr)의 객체를 그 객체가 감싸고 있는 실제 자원(Investment*)으로 변환해주어야 한다.

 

변환할 방법으로는 명시적 변환(explicit conversion)과 암시적 변환(implicit convension)이 있다.

tr1::shared_ptr과 auto_ptr은 명시적 변환을 수행하는데 get이라는 멤버 함수를 통해 실제 자원을 얻어낼 수 있다.

 

1
    int days = dayHeld(pInv.get());
cs

 

잘 설계된 스마트 포인터 클래스라면 거의 모두가 그렇듯, 포인터 역참조 연산자(operator-> 및 operator*)를 오버로딩

하고 있다. 따라서 스마트 포인터 클래스가 자신이 관리하는 실제 포인터에 대한 암시적 변환도 쉽게 가능하게 된다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
class CTest
{
public:
    void PRINT(std::string strText)
    {
        printf("%s\n",strText.c_str());
    }
};
 
CTest* CreateTest()
{
    return new CTest();
}
 
int _tmain(int argc, _TCHAR* argv[])
{
    std::tr1::shared_ptr<CTest> spTest(CreateTest());
    std::auto_ptr<CTest> apTest(CreateTest());
 
    //shared_ptr
    spTest->PRINT("AAAA");
    (*spTest).PRINT("BBBB");
 
    //auto_ptr
    apTest->PRINT("CCCC");
    (*apTest).PRINT("DDDD");
 
    return 0;
}
 
cs

 

RAII 객체 안에 들어 있는 실제 자원에 대한 접근을 매끄럽게 할 수 있도록 암시적 변환을 제공하는 설계자들도 있다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
FontHandle getFont();            //C API에서 가져온 함수, 매개변수는 생략.
 
void releaseFont(FontHandle fh); //C API에서 가져온 함수.
 
class Font
{
public:
    explicit Font(FontHandle fh) //자원 획득. 값에 의한 전달이 수행되는 것은
    : f(fh)                      //자원 해제를 C API로 하기 때문.
    {
 
    }
    ~Font()
    {
        releaseFont(f);
    }
 
private:
    FontHandle f; //실제 Font 자원

 };

cs

 

FontHand의 규모가 무척 크다고 가정하면, Font 객체를 FontHandle로 변환해야 할 경우도 무척 많을 것이다.

그래서 Font 클래스에서는 이를 위한 명시적 변환 함수로 get을 제공하도록 만들 수 있다.

 

<명시적 형변환 추가.>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
FontHandle getFont();        //C API에서 가져온 함수, 매개변수는 생략.
 
void releaseFont(FontHandle fh); //C API에서 가져온 함수.
 
class Font
{
    ...
    FontHandle get() const //명시적 변환.
    {
        return f;
    }
private:
    FontHandle f;
};
 
void changeFontSize(FontHandle f, int newsize); //폰트 API의 일부

 

 

//Form Class
int func()
{
    Font font(getFont());
    int newFontSize;
 
    ...
 
    changeFontSize(f.get(), newFontSize);    //Font에서 FontHandle로 명시적 변환.
}
cs

 

그런데 저렇게 자주 변형을 해줘야하는 경우에 사용자는 변환할 때마다 get을 호출해줘야 하기 때문에 짜증이나 Font 클래스를

사용하지 않게 될 수 있다. 그래서 FontHandle로의 암시적 변환 함수를 Font에서 제공하도록 한다.

 

<암시적 형변환 추가>

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
FontHandle getFont();        //C API에서 가져온 함수, 매개변수는 생략.
 
void releaseFont(FontHandle fh); //C API에서 가져온 함수.
 
class Font
{
    ...
    
    operator FontHandle() const //암시적 변환 함수.
    {
        return f;
    }
private:
    FontHandle f;
};
 
void changeFontSize(FontHandle f, int newsize); //폰트 API의 일부
 
//Form Class
 
int func()
{
    Font font(getFont());
    int newFontSize;
 
    ...
 
    changeFontSize(f, newFontSize);    //Font에서 FontHandle로 암시적 변환.
}
cs

 

암시적으로 변환이 되기 때문에 매끄럽게 사용하기는 좋지만 진짜 Font를 쓰려고 한 부분에서 원하지 않게 FontHandle로 바뀔 수 있다.

 

1
2
3
4
5
6
7
8
9
10
int func1()
{
    Font f1(getFont());
 
    ...
 
    FontHandle f2 = f1; //여기서 문제 발생.
                        //원래 의도는 Font 객체를 복사하는 것이었는데
                        //엉뚱하게도 f1이 FontHandle로 바뀌고 나서 복사됨.
}
cs

 

이렇게 되면 Font 객체인 f1이 관리하고 있는 Fonthandle이 f2를 통해서도 직접 사용할 수 있는 상태가 된다.

하나의 자원이 양다리를 걸치고 있는 상황은 좋지 않다. 특히 f1이 소멸되버리면 f2는 해제된 폰트를 가리키고 있는 꼴이 된다.

 

RAII 클래스를 실제 자원으로 바꾸는 방법은 특정한 용도와 사용 환경에 따라 달라지는데, 어쨌든 가장 잘 설계된 클래스라면 뒤에서 배울

맞게 쓰기에는 쉽고, 틀리게 쓰기에는 어려워야 한다.

항상 그런것은 아니지만 명시적 변환을 제공하는 쪽이 나을 때가 많다. 그러나 암시적 변환에서 생기는 사용 시의 자연스러움이 빛을 발하는

경우도 있다.

 

2줄 요약

 

 - 실제 자원을 직접 접근해야 하는 기존 API들도 많기 때문에, RAII 클래스를 만들 때는 그 클래스가 관리하는 자원을 얻을 수 있는 방법을

   열어주어야 한다.

 

 - 자원 접근은 명시적 변환과 암시적 변환이 있는데, 안정성만 따진다면 명시적 변환이 대체적으로 더 낫지만, 고객 편의성을 놓고 보면

   암시적 변환이 괜찮다. (즉, 스스로 잘 판단해서 만들어라.)

+ Recent posts