Skip to content

C++ RAII & 리소스 관리

**RAII(Resource Acquisition Is Initialization)**는 C++의 핵심 자원 관리 이디엄입니다. 자원 획득을 객체 **초기화(생성자)**에, 자원 해제를 객체 **소멸(소멸자)**에 묶어 스코프가 끝나면 자동으로 자원이 반환되도록 합니다.

// RAII 없이 — 예외 발생 시 리소스 누수
void BadFunction()
{
FILE* File = fopen("data.txt", "r"); // 획득
int* Buffer = new int[1024]; // 획득
DoRiskyWork(); // 예외 발생하면?
delete[] Buffer; // 도달 안 함 → 누수
fclose(File); // 도달 안 함 → 누수
}
// RAII 적용 — 예외가 발생해도 소멸자가 반드시 실행
void GoodFunction()
{
std::ifstream File("data.txt"); // 소멸 시 자동 close
std::unique_ptr<int[]> Buffer(new int[1024]); // 소멸 시 자동 delete[]
DoRiskyWork(); // 예외 발생해도 OK
// 스코프 종료 → Buffer, File 소멸자 자동 호출
}

RAII가 자원 관리를 보장하는 전제: 소멸자는 예외 없이 반드시 실행된다 (스택 언와인딩)


수준설명달성 방법
No-throw절대 예외를 던지지 않음noexcept 소멸자, 스왑
Strong예외 발생 시 원래 상태 완전 복원Copy-and-Swap 패턴
Basic예외 발생 시 유효한 상태 유지 (값은 변할 수 있음)RAII 기본 적용
None예외 발생 시 상태 보장 없음피해야 함

#include <memory>
// new/delete 대체
std::unique_ptr<int> Ptr = std::make_unique<int>(42);
std::cout << *Ptr; // 42
// 스코프 종료 시 자동 delete
// 배열
std::unique_ptr<int[]> Arr = std::make_unique<int[]>(100);
Arr[0] = 1;
// 스코프 종료 시 자동 delete[]
// 소유권 이전 (복사 불가, 이동만 가능)
std::unique_ptr<int> Moved = std::move(Ptr);
// Ptr는 nullptr, Moved가 소유
// 커스텀 삭제자
auto FileDeleter = [](FILE* F) { if (F) fclose(F); };
std::unique_ptr<FILE, decltype(FileDeleter)> FilePtr(fopen("data.txt", "r"), FileDeleter);
// 참조 카운팅 — 마지막 shared_ptr 소멸 시 delete
std::shared_ptr<int> SP1 = std::make_shared<int>(42);
std::shared_ptr<int> SP2 = SP1; // 참조 카운트 2
std::cout << SP1.use_count(); // 2
SP1.reset(); // 참조 카운트 1 (SP2만 남음)
std::cout << SP2.use_count(); // 1
// SP2 소멸 시 delete

순환 참조를 깨거나, 소유 없이 관찰할 때 사용합니다.

struct Node
{
int Value;
std::shared_ptr<Node> Next;
std::weak_ptr<Node> Prev; // 순환 참조 방지
};
// weak_ptr 사용
std::shared_ptr<int> SP = std::make_shared<int>(42);
std::weak_ptr<int> WP = SP;
// 실제 접근 시 lock()으로 shared_ptr 획득
if (auto Locked = WP.lock())
{
std::cout << *Locked; // 42
}
else
{
std::cout << "객체 소멸됨";
}

표준 라이브러리가 제공하지 않는 자원(OS 핸들, 네트워크 소켓, GPU 리소스 등)을 위한 RAII 클래스를 직접 작성합니다.

class FileHandle
{
public:
explicit FileHandle(const char* Path, const char* Mode)
: Handle(fopen(Path, Mode))
{
if (!Handle)
{
throw std::runtime_error(std::string("파일 열기 실패: ") + Path);
}
}
~FileHandle() noexcept
{
if (Handle)
{
fclose(Handle); // 소멸자에서 반드시 해제
}
}
// 복사 금지 (파일 핸들을 복사하면 의미가 없음)
FileHandle(const FileHandle&) = delete;
FileHandle& operator=(const FileHandle&) = delete;
// 이동은 허용
FileHandle(FileHandle&& Other) noexcept
: Handle(Other.Handle)
{
Other.Handle = nullptr; // 원본은 무효화
}
FileHandle& operator=(FileHandle&& Other) noexcept
{
if (this != &Other)
{
if (Handle) fclose(Handle);
Handle = Other.Handle;
Other.Handle = nullptr;
}
return *this;
}
FILE* Get() const noexcept { return Handle; }
bool IsValid() const noexcept { return Handle != nullptr; }
// POSIX-style read/write 래핑
size_t Read(void* Buffer, size_t Size) const
{
return fread(Buffer, 1, Size, Handle);
}
private:
FILE* Handle;
};
// 사용
void ReadFile(const char* Path)
{
FileHandle File(Path, "rb"); // 생성자에서 open
char Buffer[256];
File.Read(Buffer, sizeof(Buffer));
// 스코프 종료 시 자동 fclose
}

3.2 뮤텍스 RAII 래퍼 (직접 구현 예시)

Section titled “3.2 뮤텍스 RAII 래퍼 (직접 구현 예시)”
// std::lock_guard와 동일한 원리
template<typename Mutex>
class LockGuard
{
public:
explicit LockGuard(Mutex& M) : MutexRef(M) { MutexRef.lock(); }
~LockGuard() noexcept { MutexRef.unlock(); }
LockGuard(const LockGuard&) = delete;
LockGuard& operator=(const LockGuard&) = delete;
private:
Mutex& MutexRef;
};

임의의 정리 작업을 스코프 종료에 묶는 범용 도구입니다.

class ScopeGuard
{
public:
template<typename Func>
explicit ScopeGuard(Func&& CleanupFn)
: Cleanup(std::forward<Func>(CleanupFn))
, bActive(true)
{}
~ScopeGuard() noexcept
{
if (bActive) { Cleanup(); }
}
// 조건부 비활성화 — 성공 시 정리 취소
void Dismiss() noexcept { bActive = false; }
ScopeGuard(const ScopeGuard&) = delete;
ScopeGuard& operator=(const ScopeGuard&) = delete;
private:
std::function<void()> Cleanup;
bool bActive;
};
// 사용 예시
void TransactionalOperation()
{
StartTransaction();
ScopeGuard Guard([]() { RollbackTransaction(); }); // 실패 시 롤백
DoRiskyWork1();
DoRiskyWork2();
CommitTransaction();
Guard.Dismiss(); // 성공 — 롤백 취소
}

4. Copy-and-Swap 패턴 — 강한 예외 안전성

Section titled “4. Copy-and-Swap 패턴 — 강한 예외 안전성”
class Buffer
{
public:
explicit Buffer(size_t Size)
: Data(new int[Size])
, Size(Size)
{}
~Buffer() { delete[] Data; }
Buffer(const Buffer& Other)
: Data(new int[Other.Size]) // 여기서 예외 → 원본 unchanged
, Size(Other.Size)
{
std::copy(Other.Data, Other.Data + Size, Data);
}
// Copy-and-Swap: 강한 예외 안전성 보장
Buffer& operator=(Buffer Other) // 값으로 받아 복사 생성 (여기서 예외 가능)
{
Swap(Other); // noexcept — 절대 실패 안 함
return *this;
// Other(원래 this의 데이터)는 소멸자에서 정리
}
Buffer(Buffer&& Other) noexcept
: Data(Other.Data)
, Size(Other.Size)
{
Other.Data = nullptr;
Other.Size = 0;
}
void Swap(Buffer& Other) noexcept
{
std::swap(Data, Other.Data);
std::swap(Size, Other.Size);
}
private:
int* Data;
size_t Size;
};

class DatabaseTransaction
{
public:
explicit DatabaseTransaction(Database& DB)
: DB(DB)
, bCommitted(false)
{
DB.BeginTransaction();
}
~DatabaseTransaction() noexcept
{
if (!bCommitted)
{
DB.Rollback(); // 커밋 없이 소멸 → 자동 롤백
}
}
void Commit()
{
DB.Commit();
bCommitted = true;
}
DatabaseTransaction(const DatabaseTransaction&) = delete;
DatabaseTransaction& operator=(const DatabaseTransaction&) = delete;
private:
Database& DB;
bool bCommitted;
};
// 사용
void UpdateUserScore(Database& DB, int UserId, int Score)
{
DatabaseTransaction Tx(DB);
DB.Execute("UPDATE users SET score = ? WHERE id = ?", Score, UserId);
DB.Execute("INSERT INTO score_log VALUES (?, ?, NOW())", UserId, Score);
Tx.Commit(); // 여기에 도달하지 못하면 자동 롤백
}

// 소멸자에서 예외 던지기 — 절대 금지
class BadRAII
{
public:
~BadRAII()
{
CleanupResource(); // 예외를 던질 수 있다면?
// 스택 언와인딩 중 두 번째 예외 → std::terminate 호출
}
};
// 올바른 패턴: 소멸자에서 예외 삼키기
class GoodRAII
{
public:
~GoodRAII() noexcept
{
try
{
CleanupResource();
}
catch (const std::exception& E)
{
// 로그 기록만, 예외를 전파하지 않음
LogError(E.what());
}
}
};

자원 유형RAII 도구
힙 메모리 (단독 소유)std::unique_ptr<T>
힙 메모리 (공유 소유)std::shared_ptr<T>
약한 참조 (순환 방지)std::weak_ptr<T>
뮤텍스std::lock_guard, std::scoped_lock
파일·소켓·OS 핸들커스텀 RAII 클래스
임의 정리 작업ScopeGuard
트랜잭션커스텀 Transaction 클래스

핵심 규칙:

  • 소멸자는 반드시 noexcept — 예외가 발생하면 내부에서 삼키고 로그
  • 자원을 소유하는 클래스는 5의 법칙 준수 (소멸자·복사·이동 4개 명시)
  • std::make_unique / std::make_shared 사용 — new를 직접 쓰면 예외 안전하지 않을 수 있음
  • Copy-and-Swap 패턴으로 대입 연산자의 강한 예외 안전성 보장