30. 임시 객체의 수명과 참조 수명 연장(lifetime extension)
난이도 상내 리뷰 · C++
해설 · C++
해설 — 임시 객체의 수명과 참조 수명 연장(lifetime extension)
난이도: 상
예측 출력
A
ctor 1
B 1
ctor 2
C 2
dtor 2
D
E ctor 3
3
dtor 3
F built
G
dtor 1
핵심 개념
- 임시(temporary)의 기본 수명: 임시 객체는 그것이 등장한 전체 표현식(full expression) 이 끝나는
세미콜론에서 소멸한다.
make(3).id처럼 참조에 바인딩되지 않으면 그 줄 끝에서 바로 파괴된다. - 수명 연장(lifetime extension): 임시를 const lvalue 참조 또는 rvalue 참조에 직접 바인딩하면,
임시의 수명이 그 참조의 수명까지 연장된다.
const Tracer& r = make(1);의 임시는r이 사는 범위(여기선 main 끝)까지 산다.Tracer&& s = make(2);도 블록 끝까지 연장된다.
단계별 내부 동작 (출력 근거)
"A"출력.const Tracer& r = make(1);→ 임시Tracer(1)생성(ctor 1). r 에 바인딩되어 수명이 main 끝까지 연장."B " << r.id→B 1.- 블록 진입,
Tracer&& s = make(2);→ctor 2.C 2출력. 블록을 벗어나며 s 가 붙잡은 임시 소멸 →dtor 2. "D"출력.std::cout << "E " << make(3).id: C++17에서<<는 좌변이 우변보다 먼저 시퀀스된다. 그래서"E "가 먼저 나가고(E), 그 다음make(3)가 임시를 만든다(ctor 3),.id(=3) 출력(3). 이 임시는 이 전체 표현식의 끝(세미콜론)에서 소멸 →dtor 3.std::string_view sv = std::string("hello world").substr(0,5);—substr이 만든 임시std::string은 이 줄 끝에서 소멸한다.sv는 이미 소멸한 문자열의 내부 버퍼를 가리키는 dangling view 가 된다 (여기서는 접근하지 않아 UB를 실행하진 않지만, 이후sv사용은 UB).F built만 출력."G"출력, main 반환. 마지막으로 r 이 붙잡은 임시(1) 소멸 →dtor 1.
흔한 오해·함정
- 함수를 거치면 연장되지 않는다.
const T& identity(const T& t){ return t; }에 대해const T& r = identity(make(5));는 연장되지 않는다. 수명 연장은 임시를 참조에 직접 바인딩할 때만 적용된다.identity안의 매개변수t는 임시를 가리키지만, 반환된 참조를 바깥r에 바인딩하는 것은 "임시 → 참조" 직접 바인딩이 아니다. 임시는 그 줄 끝에서 죽고r은 dangling. - 멤버 참조로는 전체 임시가 연장되지 않는다(주의 규칙). 집합체 초기화 등 특수 상황에서 멤버 참조에 임시를 바인딩하면 규칙이 미묘하다 — 일반적으로 클래스 멤버 참조는 임시 수명을 연장하지 않는다.
- 뷰 타입(string_view, span)의 dangling: 뷰는 소유하지 않는다. rvalue(임시 컨테이너)에서 뷰를 얻으면
원본이 즉시 소멸해 dangling.
std::string_view sv = get_string();(반환이 값이면) 같은 코드가 대표적 버그. auto&&와 범위 기반 for:for (auto&& x : makeVector())는 makeVector()의 임시가 루프 동안 연장되어 안전하다. 하지만for (auto x : makeVector().items())처럼 멤버가 반환한 뷰/참조를 순회하면 임시 컨테이너는 죽어 dangling 이 될 수 있다.
면접 포인트
- 수명 연장이 적용되는 정확한 조건(임시를 const&/&& 에 직접 바인딩)과, 함수 반환을 거치면 깨지는 이유.
string_view/span이 왜 "빌려온 뷰"이며 rvalue 원본과 만나면 위험한지.- C++17에서
<<연산자 피연산자의 시퀀스 규칙(좌변이 우변보다 먼저)이 출력 순서에 주는 영향.