← 문제로

39. std::optional 의 내부 동작과 값 의미론

난이도 중
내 리뷰 · C++
해설 · C++

해설 — std::optional 의 내부 동작과 값 의미론

난이도: 중

요약

std::optional<T> 는 "값이 있을 수도, 없을 수도" 를 힙 할당 없이 표현한다. 내부적으로 T 크기의 정렬된 저장소 + engaged(bool) 플래그를 가지며, 담긴 객체는 그 저장소에 in-place 로 생성/소멸된다. 출력:

-- 1 --
engaged=0
-- 2 --
ctor A
engaged=1
-- 3 --
dtor A
engaged=0
-- 4 --
ctor B
move B
dtor 
operator-> 접근
name=B
-- 5 --
fallback()
r=FB
-- end --
dtor B

핵심 개념

  • 저장소: 대략 union { char none; T value; }; bool engaged; 형태. 힙을 쓰지 않으므로 sizeof(optional<T>) >= sizeof(T) + 정렬 패딩. 포인터/힙 간접 참조가 없다.
  • engaged 플래그: 값의 유무. has_value()/operator bool 이 이를 반환.
  • 수명 관리: emplace/생성자가 저장소에 T 를 생성, reset/소멸자가 T 를 소멸. 비어 있으면 T 의 생성자·소멸자는 호출되지 않는다.

단계별 내부 동작

  1. optional<Tracked> o;: 비어 있는 optional. Tracked 생성자 호출 없음 → 출력 없음, engaged=0.
  2. o.emplace("A"): 저장소에 Tracked("A")직접 생성(ctor A). 임시 없이 in-place 구성이라 복사/이동이 없다. engaged=1.
  3. o.reset(): 담긴 Tracked 소멸(dtor A), engaged=0.
  4. optional<Tracked> p{Tracked("B")}: 먼저 임시 Tracked("B") 생성(ctor B) → optional 저장소로 이동 생성(move B) → 임시 소멸. 이때 이동당한 임시의 name 은 비어 있어 dtor (빈 이름) 출력. p->nameoperator->(전제조건: engaged 이어야 함, 검사 없음)로 "B" 접근.
  5. q 에 대한 q ? q->name : fallback(): q 가 비어 operator bool 이 false → fallback() 평가(fallback() 출력, "FB" 반환). 만약 q->name 을 먼저 평가했다면 전제조건 위반(UB)이었을 것.
  6. 프로그램 종료: p 가 아직 값을 담고 있으므로 스코프 끝에서 dtor B.

흔한 오해·함정

  • operator / operator-> vs value()*: *opt/opt->전제조건이 engaged 이며 비어 있으면 정의되지 않은 동작(검사 안 함). opt.value() 는 비어 있으면 std::bad_optional_access 예외. 안전이 필요하면 value() 또는 사전 검사.
  • value_or 의 인자는 항상 평가된다: opt.value_or(expensive()) 는 opt 유무와 무관하게 expensive() 를 호출한다(값 인자). 무거운 fallback 은 본문처럼 opt ? *opt : compute() 로 지연 평가해야 한다 — 본 예제의 5번이 그 관용구.
  • 참조는 못 담는다: std::optional<T&> 는 표준에서 ill-formed. 참조 유무는 포인터(T*)나 reference_wrapper 로 표현.
  • moved-from 상태: 4번에서 optional 로 이동한 뒤 임시의 name 은 "유효하지만 미지정"(여기선 빈 문자열). optional 자체는 여전히 engaged 상태로 이동될 수 있고, std::move(opt) 는 담긴 값만 이동하며 opt 는 여전히 engaged(빈 값이 아님)로 남는다.
  • 비었을 때 비용 0 아님: 값이 없어도 sizeof(T) 만큼 저장소를 점유한다. 큰 T 를 optional 로 많이 두면 메모리 낭비 → 그럴 땐 포인터/unique_ptr 고려.

면접 포인트

  • optional 이 힙을 쓰지 않고 in-place 저장한다는 점과 그 메모리 레이아웃(union + flag), sizeof 함의.
  • operator*(전제조건, UB) vs value()(예외) vs value_or(항상 인자 평가)의 차이와 안전한 사용 관용구.
  • emplace/reset 이 담긴 객체의 생성자·소멸자를 정확히 언제 호출하는지, 임시를 거친 초기화의 이동 발생.