[DAY32] Test Recap(4) : RAII, Smart Pointer

베리투스·2025년 9월 17일

TIL: Today I Learned

목록 보기
33/93

오늘은 C++의 메모리 관리 핵심인 RAII와 스마트 포인터에 대해 깊이 파고들었다. 개념은 안다고 생각했는데, 모의 면접을 통해 내가 '무엇'인지는 알지만 '왜' 그렇게 동작하는지에 대한 이해가 부족했다는 것을 알게 되었다. 🧐 RAII가 단순히 '소멸자'를 의미하는 것이 아니라 '스코프를 이용한 자동 관리 메커니즘'이라는 더 큰 그림을 보게 되었다. 또한, make_shared를 써야 하는 이유가 단순한 편의성 때문이 아니라는 사실에 머리를 한 대 맞은 것 같았다.


📌 목표

  • RAII가 '스코프 기반 자동 관리 메커니즘'임을 이해하기
  • unique_ptr가 RAII를 어떻게 구현하는지 파악하기
  • make_sharednew보다 나은 구체적인 이유(성능, 예외 안전성) 알기

📖 이론

1. RAII, 다시 제대로 이해하기

RAII(Resource Acquisition Is Initialization)는 "자원 획득은 초기화다"라는 뜻이다. 이전까지 나는 이걸 '생성자에서 자원을 얻고, 소멸자에서 해제하는 것' 정도로만 이해했다. 틀린 말은 아니지만, 핵심을 놓치고 있었다.

RAII의 진짜 핵심은 '객체의 생명주기(lifetime)'를 '자원의 생명주기'에 묶어, 스코프(scope)를 벗어날 때 소멸자가 자동으로 호출되는 메커니즘을 활용하는 것이다. 즉, unique_ptr<MyClass> 객체를 선언하는 행위 자체가 RAII를 실천하는 것이다. 이 스마트 포인터 객체가 스코프를 벗어나면, C++ 언어 규칙에 따라 소멸자가 무조건 호출되고, 이 소멸자가 내부적으로 delete를 호출해주는 것이다.

2. 왜 make_shared를 써야 할까?

shared_ptr<MyClass> p(new MyClass()); 보다 auto p = make_shared<MyClass>(); 가 더 좋은 이유가 명확해졌다.

  • 1. 성능 최적화 (메모리 할당)

    • new 사용 시: (1) MyClass 객체를 위한 메모리 할당, (2) 참조 카운트 등을 관리하는 '제어 블록'을 위한 메모리 할당. 이렇게 두 번의 동적 할당이 일어난다.
    • make_shared 사용 시: MyClass 객체와 '제어 블록'에 필요한 메모리를 한 번의 동적 할당으로 묶어서 처리한다. 메모리 할당은 비싼 작업이므로, 한 번으로 줄이는 것이 훨씬 효율적이다. 👍
  • 2. 예외 안전성 (Exception Safety)
    아래와 같은 코드가 있다고 가정해보자.
    function(shared_ptr<MyClass>(new MyClass()), function_that_might_throw());
    컴파일러는 코드 실행 순서를 바꿀 수 있다. 만약 (1)new MyClass() 호출 → (2)function_throw() 호출(여기서 예외 발생!) → (3)shared_ptr 생성자 호출 순으로 실행된다면? 예외 때문에 shared_ptr는 생성되지도 못하고, new로 할당된 메모리는 그대로 누수된다.
    하지만 make_shared를 쓰면 메모리 할당과 shared_ptr 생성이 하나의 함수 안에서 일어나므로 이런 문제를 원천적으로 방지할 수 있다.


⚠️ 실수

  • RAII를 '소멸자에서 자원을 해제하는 것'으로만 한정해서 생각했다.

    • (X) RAII는 소멸자 코드 그 자체다.
    • (O) RAII의 핵심은 객체의 스코프를 이용해 자원을 자동으로 관리하는 메커니즘 자체이다. unique_ptr를 선언하고 사용하는 행위 자체가 RAII 원칙을 실천하는 것이다. 소멸자는 그 메커니즘을 완성하는 부품일 뿐이다.
  • make_shared가 그냥 new를 쓰는 것보다 편한 문법 정도로만 생각했다.

    • (X) make_shared는 코드를 짧게 만들어주는 역할이다.
    • (O) 두 가지 핵심적인 이유가 있다.
      1. 성능 최적화: 객체와 제어 블록 메모리를 한 번에 할당하여 더 효율적이다.
      2. 예외 안전성: 복잡한 표현식에서 발생할 수 있는 미묘한 메모리 누수 가능성을 원천적으로 방지한다.

✅ 핵심 요약

개념설명비고
RAII자원의 생명주기를 객체의 생명주기에 묶는 C++ 핵심 패턴.스코프를 벗어날 때 소멸자가 자동 호출되는 것이 핵심.
unique_ptr유일한 소유권을 가지는 스마트 포인터. 복사 불가, 이동 가능.RAII 패턴의 가장 기본적인 구현체.
shared_ptr참조 카운팅 기반으로 여러 포인터가 자원을 공유하는 스마트 포인터.순환 참조 문제를 주의해야 함.
make_unique, make_shared스마트 포인터를 생성하는 팩토리 함수.new를 직접 사용하는 것보다 성능예외 안전성 면에서 항상 우수함.
profile
Shin Ji Yong // Unreal Engine 5 공부중입니다~

0개의 댓글