본문 바로가기

Effective C++/3. 자원 관리

항목 15: 자원 관리 클래스에서 관리하는 자원은 외부에서 접근할 수 있도록 하자

자원 관리 클래스 == good class

 

실수로 터질 있는 자원 누출은 튼튼히 막아 주는 보호벽 역할을 한다. 똑바로 설계된 시스템이면 당연히 자원 누출은 없어야 한다. 하지만 세상은 만만치 않다. 현장에서 열심히 쓰이고 있는 수많은 API들이 자원을 직접 참조하도록 되있어서, 자원 관리 객체를 넘어가서 직접 자원을 주물러야 하는 일이 있을 수 밖에 없다…

 

 

우리는 dayHeld 다음과 같이 쓰고 싶은데 타입이 안맞음

타입을 맞추는 방법 : 명시적, 암시적 반환

 

  • 명시적: RAII 클래스(shared_ptr) get() 이라는 함수를 제공함, 함수를 사용하면 타입으로 만든 스마트 포인터 객체에 들어 있는 실제 포인터( 사본) 얻을 있다.

 

 

  • 암시적: 제대로된 스마트 포인터들은 역참조 연산(->, *) 등에 대해 overloading 지원, 그래서 암시적 변환도 쉽게 가능

 

 

 

RAII 객체 안에 들어 있는 실제 자원(Investment) 얻어낼 필요가 종종 있기 때문에, RAII 설계자 중에선 암시적 변환 함수를 제공하여 자원의 접근을 매끄럽게 있도록 만드는 분도 계신다.

 

예를 들어 C API 직접 조작 가능한 폰트를 RAII 클래스로 둘러싸서 쓰는 경우를 생각해보자.

 

 

FontHandle int 해놨지만, 만약 매우 구조체라고 생각해보자. 그럼 Font 객체(RAII 객체) FontHandle(자원)으로 변환해야 경우도 당연히 있겠다.(자원을 직접 컨트롤 하기 위해서) 그러면 Font 클래스는 변환을 명시적으로 지원하기 위해 get() 함수를 제공할 있다.

 

 

이렇게 해두면 get함수로 자원 관리 객체 내부의 자원에 접근할 있다.(examiner 함수)

근데 문제가 하나 있는데, 사용자는 하부 수준 C API(FontHandle 직접 매개변수로 받는 함수들) 때마다 get() 호출해야 한다.

 

 

 

변환할때마다 무슨 함수를 호출해야한다? 짜증나서 Font 객체를 안쓰는 개발자들이 늘어날 것이다. => 안쓰면 만든 이유가 없다. 애초에 자원 누출안될려고 만드는게 RAII 객체인데

 

대안 -> 명시적 + 암시적 변환 함수도 제공한다.

 

 

원리를 설명하자면, 매개변수에 어떤 객체가 들어갈 함수에서 지정한 매개변수의 타입과 일치하지 않으면 컴파일러가 자동으로 변환이 되는지 확인하는데, 변환 함수를 따로 정의하면 함수가 호출된다.

 

이러면 get 쓰고도 자원에 접근이 가능해진다. 하지만 방법도 마냥 좋은 것은 아니다.. 암시적 변환이 들어가면 실수를 저지를 여지가 많아진다. 암시적 변환을 원하지 않을 때에도 자동으로 변환이 되는 경우가 있다.

 

 

 

바로 이런 경우인데, f1 있는 자원만 f2 복사하고 싶었는데, Font 객체인 f1 관리하고 있는 폰트(FontHandle) f2 통해서도 직접 사용할 있는 상태가 되었다. 왜냐하면 복사되기 변환 함수(FontHandle()) 자원 관리 객체 내부의 FontHandle 직접 리턴하였기 때문이다.

 

이러면 f1 소멸되면 f2 당연히 같이 소멸된다. (자원이 양다리를 걸침)

 

=> 쉽지 않다…

 

RAII 클래스를 실제 자원으로 바꾸는 방법으로서 명시적 방법을 쓸지(get()), 암시적 방법을 쓸지(역참조 연산자 오버로딩, 형변환 함수) 결정하는 것은 RAII 클래스의 용도와 환경에 따라 달라진다. 가지 가이드라인을 제시하면

 

"올바르게 쓰기에는 쉽게, 틀리게 쓰기에는 어렵게"

 

일반적으로는 암시적 변환보단 get()같은 명시적 변환 함수를 제공하는 것이 나을 때가 많다. 원하지 않는 변환이 일어날 여지를 줄여주기 때문이다. 하지만 암시적 타입 변환에서 생기는 자연스러움이 빛을 발하는 경우도 있기 때문에 내가 설계하는 RAII 클래스의 용도와 환경에 맞게 선택하는 것이 맞겠다.

 

혹시 이런 설계(실제 자원을 사용자에 제공) 캡슐화에 위배된다고 생각할 수도 있지만, 애초에 RAII 클래스는 애초부터 데이터의 은닉이 목적이 아니라, 원하는 동작(자원 관리 -> 자원의 해제) 실수 없이 이루어지도록 하면 OK이다. 굳이 캡슐화를 원한다면 자원 해제라는 핵심 기능, 하지만 사용자가 원리를 필요 없는 이런 기능을 캡슐화할 있겠다.

 

shared_ptr 같은 시중에 설계된 RAII 클래스들은 보면 참조 카운팅 메커니즘에 필요한 장치들은 모두 캡슐화하고 있지만, 자신이 관리하는 포인터를 쉽게 접근할 있는 통로도 마련해놓았다.

사용자가 볼 필요 없는 데이터는 감추고, 고객 차원에서 접근해야 하는 데이터는 열어 주는 것이 핵심이다.

 

 

  • 실제 자원을 직접 접근해야 하는 기존 API들도 많기 때문에, RAII 클래스를 만들 때는 클래스가 관리하는 자원을 얻을 수 있는 방법을 열어 주어야 한다.
  • 자원 접근은 명시적 변환 혹은 암시적 변환을 통해 가능하다. 안전성만 따지면 명시적 변환이 낫지만, 고객 편의성을 놓고 보면 암시적 변환이 괜찮다.