← Back to list

[디자인 패턴] RAII

RAII

Mangryang · 2026-06-26 17:02 · 0 claps · 5.2 min read
#raii
Open on Medium ↗

[디자인 패턴] RAII

RAII

RAII?

RAII가 뭔가요? Resource Acquisition Is Initialization이요 (。•̀ᴗ-)✧

RAII가 뭔가요? Resource Acquisition Is Initialization이요 (。•̀ᴗ-)✧

Resource Acquisition Is Initialization, 자원 획득은 초기화에서! 라는 의미로, RAII가 바로 이 원칙의 약자이다.

RAII를 디자인 패턴의 하나로 분류하기도 하지만, 사실 특정한 구조를 제시하는 GoF 23과 같은 디자인 패턴보다는 C++ 전반에서 쓰이는 관용구이자 설계 원칙에 가깝다.

RAII자원의 생명 주기를 객체에 동기화하는 설계 방식이다.

이전 스토리에서 다룬 스마트 포인터도 RAII 원칙을 이용한 문법이다.

[embed][C++] 포인터 C++ 정리하기medium.com

RAII 예시 코드

#include <cstdio>
#include <stdexcept>
#include <iostream>

// 파일에 읽기 전용으로 접근하기 위한 클래스 (RAII)
class File
{
public:
    // 생성자
    explicit File(const char* path) : m_file(std::fopen(path, "r"))
    {
        if (m_file == nullptr)
        {
            throw std::runtime_error("Opening files Error");
        }
    }

    // 소멸자
    ~File()
    {
        if (m_file != nullptr)
        {
            std::fclose(m_file);
        }
    }

    // 복사 생성자 및 복사 대입 연산자 삭제
    File(const File&) = delete;
    File& operator=(const File&) = delete;

    FILE* Get() const
    {
        return m_file;
    }

private:
    FILE* m_file;
};

void Process()
{
    try
    {
        // File 클래스 객체 생성
        File file("data.txt");

        // 파일 처리
        std::cout << "Executing...." << std::endl;

        // 예외 발생 가정
        bool errorOccurred = false;
        if (errorOccurred)
        {
            throw std::runtime_error("Processing files Error");
        }
    }

    // 런타임 예외 처리
    catch (const std::runtime_error& e)
    {
        std::cerr << "Error occurred: " << e.what() << std::endl;
    }

    // 알 수 없는 예외 처리
    catch (...)
    {
        std::cerr << "An unknown error has occurred!" << std::endl;
    }
}

이 예시 코드의 File 클래스RAII 원칙에 따라 작성되어 있다. File 객체가 생성될 때, 즉 생성자에서 std::fopen() 함수를 통해 파일을 연다. File 객체가 소멸할 때, 즉 소멸자에서 std::fclose() 함수를 통해 파일을 닫는다. 이를 통해 파일 자원의 생명 주기가 File 객체의 생명 주기와 동기화된다.

RAII 원칙을 준수하는 것의 이점

그렇다면, RAII 원칙을 준수하는 것의 이점은 무엇일까?

RAII 원칙을 준수하는 가장 큰 이점은 자원 관리 책임을 제어 흐름이 아니라 객체의 생명 주기에 맡길 수 있다는 점이다.

예시 코드에서 예외가 발생하여 함수가 정상적인 실행 경로를 벗어나는 경우에도 객체의 소멸과 동시에 소멸자가 호출되어 자원이 안전하게 해제되는 것을 확인할 수 있다.

RAII를 고려해야 하는 상황

RAII가 설계 원칙이긴 하나, 모든 클래스에 기계적으로 적용해야 한다는 것은 아니다. 다만, 클래스나 함수가 특정 자원의 소유권을 가진다면 RAII 적용을 우선적으로 고려해야 한다.

대표적으로, 다음과 같은 상황에서는 RAII 원칙 적용을 고려해 보자.

  • 반드시 해제해야 하는 자원을 관리하는 경우

동적 메모리, 파일, 소켓과 같은 자원은 사용이 끝나면 반드시 해제해야 한다.

이러한 자원을 수동으로 관리하면 정상적인 실행 경로뿐만 아니라 조기 반환이나 예외가 발생하는 경로에서도 해제 함수를 직접 호출해야 한다. 이 과정에서 특정 경로의 해제 코드를 누락하면 메모리 누수, 파일 핸들 누수 등의 문제가 발생할 수 있다.

RAII를 적용하면 자원을 소유한 객체가 소멸할 때 자원을 자동으로 해제한다. 따라서 자원의 소유권과 해제 책임이 객체에 명확하게 귀속되며, 호출자가 모든 실행 경로에 해제 코드를 작성해야 하는 부담도 줄어들게 된다.

  • 함수에 조기 반환이나 예외 발생 가능성이 있는 경우

함수 내부에 여러 개의 반환 지점이 있거나 예외가 발생할 수 있는 경우에도 RAII를 우선적으로 고려해야 한다.

자원을 RAII 객체로 관리하면 Stack Unwinding 덕분에 어떤 경로로 함수를 빠져나가든 무조건 소멸자가 호출되므로, 자원을 일관되게 해제할 수 있다.

Stack Unwinding

Stack Unwinding

  • 상호 배타적인 연산이 있는 경우

트랜잭션의 BEGIN - COMMIT/ROLLBACK, 뮤텍스의 lock - unlock 등의 일부 연산은 시작과 종료가 반드시 한 쌍으로 수행되어야 한다.

이렇게 반드시 짝을 이루어야 하는 연산을 객체의 생성과 소멸에 연결하면 해제 연산의 누락을 방지할 수 있다.

  • 여러 자원을 동시에 관리하는 경우

여러 개의 자원을 동시에 다루어야 할 때 RAII를 사용하지 않으면, 자원을 획득한 순서와 해제할 순서를 직접 관리해야 한다.

특히 중간에 자원 획득이 실패하거나 예외가 발생하면, 이미 획득한 자원만 골라서 역순으로 해제해야 한다. 자원의 개수가 많아질수록 이러한 정리 로직은 복잡해진다.

RAII를 적용하면 각 객체가 자신이 소유한 자원을 개별적으로 관리한다. 스코프를 벗어날 때 객체는 생성된 순서의 역순으로 소멸하므로, 개발자가 직접 처리 로직을 작성하지 않아도 안전하게 자원들이 해제된다.


메타데이터
post_id
fd8a7af4dc1b
slug
디자인-패턴-raii-fd8a7af4dc1b
url
https://medium.com/@mangryang.dev/%EB%94%94%EC%9E%90%EC%9D%B8-%ED%8C%A8%ED%84%B4-raii-fd8a7af4dc1b
canonical_url
https://medium.com/@mangryang.dev/%EB%94%94%EC%9E%90%EC%9D%B8-%ED%8C%A8%ED%84%B4-raii-fd8a7af4dc1b
author_url
https://medium.com/@mangryang.dev
status
ok
fetched_at
2026-07-09 16:18:44