스토리텔링 개발자

[More Effective C++] 9. 자원 관리 객체(RAII) 본문

개발/More Effective C++

[More Effective C++] 9. 자원 관리 객체(RAII)

김디트 2024. 8. 9. 10:46
728x90

항목 9 : 리소스 누수를 피하는 방법의 정공은 소멸자이다.

 

 

 

지역 리소스에 대한 포인터
  • 지역 리소스(스택에서 생긴 리소스)를 조작할 때, 포인터는 부적절할 수 있다.
    • 소멸자가 불리지 않는 상황이 발생할 수 있기 때문이다.
    • 즉, 리소스 누수가 발생할 수 있다는 뜻이다.
class ALA
{
public:
    virtual void processAdoption() = 0;
    ...
};

class Puppy : public ALA
{
public:
    virtual void processAdoption();
    ...
};

class Kitten : public ALA
{
public:
    virtual void processAdoption();
    ...
};

// s로부터 동물 정보를 읽어서
// 적절한 타입의 객체를 동적할당 후
// 포인터 반환
ALA* readALA(istream& s) { ... }

void processAdoptions(istream& dataSource)
{
    while(dataSource)
    {
        ALA* pa = readALA(dataSource); // 객체 생성
        pa->processAdoption(); // 가상 함수 처리
        delete pa; // 객체 삭제
    }
}
  • 헌데 위의 로직에서 '가상 함수 처리'(pa->processAdoption()) 중 예외가 발생하면?
    • delete가 불리지 않으면서 리소스 누수가 발생한다.
  • 예외를 처리
void processAdoptions(istream& dataSource)
{
    while(dataSource)
    {
        ALA* pa = readALA(dataSource);
        
        try
        {
            pa->processAdoption();
        }
        catch(...)
        {
            delete pa; // 객체 삭제
            throw; // 예외 전파
        }
        
        delete pa; // 객체 삭제
    }
}
  • 문제점
    • try - catch 블록으로 코드가 복잡해진다.
    • 코드 중복이 발생한다.( delete pa )

 

 

 

예외 발생 시 소멸자를 반드시 호출하는 방법
  • 포인터 대신 포인터처럼 동작하는 객체, 즉 스마트 포인터를 사용한다.(항목 28 참조)
  • 자신의 유효범위(scope)를 벗어나면 자신이 가리키는 메모리를 삭제한다.
template<typename T>
class auto_ptr
{
public:
    auto_ptr(T* p = 0) : ptr(p) {}
    ~auto_ptr() { delete ptr; }
private:
    T* ptr;
};
void processAdoptions(istream& dataSource)
{
    while(dataSource)
    {
        auto_ptr<ALA> pa(readALA(dataSource)); // 스마트 포인터 사용으로 대체
        pa->processAdoption();
    ]
}
  • 다만 이는 단일 객체에 대해서만 유효하며, 배열 포인터의 경우 다른 방도가 필요하다.
    • 스마트 포인터가 단일 객체 형태의 delete를 사용하기 때문이다. (항목 8 참조)
    • 배열 포인터에 대해 auto_ptr처럼 동작하는 클래스가 필요하다면 만들어야 한다.
    • 하지만 vector 같은 STL 컨테이너를 쓰는 것이 훨씬 나은 선택이다.

 

 

 

자원 관리 객체를 사용하는 다른 예
void displayInfo(const Information& info)
{
    WINDOW_HANDLE w(createWindow()); // 윈도우 리소스 획득
    
    w를 핸들로 하는 윈도우에 정보를 표시한다;
    
    destroyWindow(w); // 윈도우 리소스 해제
}
  • 리소스가 포인터는 아니지만, 윈도우 리소스가 해제되지 않으면 리소스 누수가 발생한다.
  • 자원 관리 객체로 자원 획득 / 해제를 컨트롤하도록 수정한다.
class WindowHandle
{
public:
    WindowHandle(WINDOW_HANDLE handle) : w(handle) {}
    ~WindowHandle() { destroyWindow(w); }
    
    operator WINDOW_HANDLE() { return w; } // WINDOW_HANDLE로의 암시적 변환 지원
private:
    WINDOW_HANDLE w;
    
    // 복사 생성자 자동 생성 막기(항목 28 참조)
    WindowHandle(const WindowHandle&);
    WindowHandle& oepratr=(const WindowHandle&);
};

void displayInfo(const Information& info)
{
    WindowHandle w(createWindow());
    
    w를 핸들로 하는 윈도우에 정보를 표시한다;
}
  • 암시적 변환 지원을 하고 있으나, 일반적으로는 좋지 않은 생각이다.(항목 5 참조)
  • 복사 동작을 막아뒀다.(EC++ 항목 14 참조)

 

 

 

참고
 

[Effective C++] 13. 자원 관리 객체(RAII)

항목 13 : 자원 관리에는 객체가 그만!   new / delete 문의 짝을 맞추지 못하게 되는 상황class Investment { ... };Investment* createInvestment();void f(){ Investment* pInv = createInvestment(); ... // pInv 사용부. // 하지만

delightlane.tistory.com

 

728x90
Comments