[Effective C++ 정리 #15] RAII의 Raw resource 접근 방법
이 글은 『Effective C++』를 읽고 개인적으로 공부한 내용을 정리한 기록입니다.
저는 컴퓨터공학을 전공하지 않았으며, 프로그래밍을 공부하는 과정에서의 이해와 생각을 정리하기 위해 글을 작성하고 있습니다.
따라서 내용 중 일부에 오류나 부정확한 설명이 있을 수 있으며, 피드백은 언제든지 환영합니다. 확인 후 수정하도록 하겠습니다.
전문적인 해설이 아닌 개인적 시선에서의 정리임을 참고하고 읽어주시면 감사하겠습니다.
[Effective C++ 정리 #15] 리소스 관리 클래스에서는 원시 자원 접근을 제공!
C++에서 RAII(Resource Acquisition Is Initialization)는 자원 관리를 훌륭하게 해결합니다. RAII 객체를 통해 메모리 해제, 파일 닫기, 락 해제 등 자원 해제를 자동으로 처리할 수 있습니다. 이상적으로는 프로그램 전체가 RAII 객체만 사용하면 됩니다.
하지만 현실 세계에서는 그렇게 깔끔하게만 동작하지 않습니다. 많은 API들이 여전히 원시 자원(raw resource)을 요구하기 때문입니다. 이럴 때 RAII 객체 내부에 숨겨진 자원에 접근하는 방법이 필요합니다.
문제 상황
이전 아이템에서 소개된 예시를 다시 가져옵니다. RAII 객체인 shared_ptr
로 자원을 관리하고 있습니다:
std::tr1::shared_ptr<Investment> pInv(createInvestment());
이제 다음과 같은 API를 호출하려고 합니다:
int daysHeld(const Investment* pi);
int days = daysHeld(pInv); // 오류 발생!
하지만 API는 Investment*
를 기대하는데 shared_ptr
객체를 넘기고 있기 때문에 컴파일러는 에러를 발생합니다!
따라서, RAII 클래스 내부의 원시 포인터를 꺼내는 방법이 필요합니다.
해결 방법!
원시 자원에 접근하는 방법은 다음 두 가지로 나뉩니다:
- 명시적 변환(explicit conversion):
get()
같은 멤버 함수를 제공 - 암시적 변환(implicit conversion): 포인터 변환 연산자(operator)를 오버로딩
각 방식은 장단점이 다릅니다.
1. 명시적 변환: get()을 사용
shared_ptr
와 auto_ptr
모두 get()
함수를 제공합니다. 이를 이용해 원시 포인터를 얻습니다:
int days = daysHeld(pInv.get()); // 정상적으로 컴파일됩니다
명시적 변환 방식은 아래의 장점이 있습니다:
- 의도가 명확. 어떤 타입 변환이 발생하는지 한눈에 확인 가능
- 암묵적인 잘못된 변환을 방지
대신 사용자는 매번 get()
을 호출해야 하는 번거로움이 있습니다.
2. 암시적 변환: operator overloading
스마트 포인터들은 보통 operator->
와 operator*
를 오버로딩합니다. 이를 통해 마치 원시 포인터처럼 사용할 수 있습니다:
bool taxable = !(pInv->isTaxFree());
이런 연산자 오버로딩 덕분에 RAII 객체를 사용할 때 일반 포인터처럼 자연스럽게 활용할 수 있습니다.
나아가 어떤 클래스는 포인터로의 암묵적 변환 연산자까지 제공합니다:
class Font {
public:
operator FontHandle() const { return f; }
private:
FontHandle f;
};
이렇게 하면 C 스타일 API를 호출할 때도 매끄럽게 사용 가능합니다:
Font f(getFont());
changeFontSize(f, newSize); // Font가 자동으로 FontHandle로 변환됩니다
암시적 변환은 편리하지만 위험 요소도 가지고 있습니다.
Font f1(getFont());
FontHandle f2 = f1; // 의도하지 않았지만 암묵적 변환 발생
이 코드는 Font
객체를 복사하려는 의도였지만, 실제로는 내부의 FontHandle
값만 복사되고 있습니다. 이로 인해 RAII 객체와 별도로 원시 자원이 중복 관리되는 위험한 상황이 발생할 수 있습니다.
따라서 대부분의 경우에는 명시적 변환이 더 안전합니다!!
RAII 클래스가 캡슐화를 위반하는 것일까요?
일견 보면 내부의 원시 자원을 외부로 꺼내는 행위는 캡슐화(encapsulation)를 깨는 것처럼 보입니다. 하지만 RAII 클래스의 1차 목적은 **캡슐화가 아니라 자원 해제를 보장하는 것**입니다.
실제로 shared_ptr
도 참조 카운트 관리와 메모리 해제를 캡슐화하지만, 내부의 원시 포인터는 쉽게 꺼내 쓸 수 있도록 제공합니다.
잘 설계된 RAII 클래스란: 숨겨야 할 복잡한 관리 로직은 감추고, 클라이언트가 반드시 알아야 하는 부분은 제공하는 클래스입니다.
핵심 요약
- RAII 클래스는 내부 자원에 접근하는 수단을 제공한다.
- 명시적 변환(get())은 안전하고 명확하며, 암시적 변환(operator) 은 편리하지만 의도치 않은 동작을 유발할 수 있다.
감사합니다!