2. 이동 의미론과 복사 생략(RVO/NRVO)
난이도 중내 리뷰 · C++
해설 · C++
해설 — 이동 의미론과 복사 생략(RVO/NRVO)
난이도: 중상
답변 프레임워크: 요약 → 핵심 개념 → 단계별 내부 동작 → 흔한 오해·함정 → 면접 포인트
요약
g++ -std=c++17 에서 예상 출력(흔한 NRVO 적용 가정):
ctor A
---
ctor B
---
ctor C
move C
dtor
===
dtor C
dtor B
dtor A
makeRVO(): prvalue 반환 → C++17 의 보장된 복사 생략. 복사도 이동도 없다.a가 곧바로 그 자리에서 생성. 출력은ctor A하나.makeNRVO(): 이름있는 지역 반환 → NRVO(선택적). g++ 는 적용한다. 출력ctor B하나.makeMovedReturn():return std::move(t)→ NRVO 가 불가능해져 이동 생성자가 호출됨.ctor C→move C(t.name 을 가져감) → 그 후 지역t소멸 시 name 이 빈 문자열이라dtor(뒤에 공백) 출력. std::move 가 오히려 이동 1회 + 추가 소멸 출력을 만든다.
핵심 개념
- 복사 생략(copy elision): 컴파일러가 임시/지역 객체의 복사·이동을 없애고 대상 자리에서
직접 생성하는 최적화.
- 보장된 RVO (C++17): prvalue(이름 없는 임시)를 같은 타입 객체 초기화/반환에 쓰면
복사·이동이 반드시 제거된다(이동 생성자가 없어도 동작).
Tracer a = makeRVO();그리고return Tracer("A");가 여기 해당. - NRVO (Named RVO): 이름있는 지역을 반환할 때의 elision. 표준은 허용하지만 의무는 아니다. 대부분 컴파일러가 적용하나, 분기·다중 return 등으로 못 할 수 있다.
- 보장된 RVO (C++17): prvalue(이름 없는 임시)를 같은 타입 객체 초기화/반환에 쓰면
복사·이동이 반드시 제거된다(이동 생성자가 없어도 동작).
- return 의 암묵적 이동: 지역을 반환하면 오버로드 결정에서 그 지역을 먼저 rvalue 로 취급한다(이동 생성자 우선). 그래서 NRVO 가 안 되더라도 보통 "이동"이지 "복사"가 아니다.
단계별 내부 동작
Tracer a = makeRVO();return Tracer("A")는 prvalue. C++17 규칙으로 임시는 만들어지지 않고a자리에서 직접 생성 →ctor A만. (생성자 안name(std::move(n))은 std::string 의 이동이라 Tracer 의 출력엔 안 보임.)
Tracer b = makeNRVO();- 지역
t("B")생성(ctor B).return t;에서 NRVO 로t가 곧b→ 추가 이동·복사 없음. (NRVO 미적용 가정 시엔move B+ 빈dtor가 더 보인다.)
- 지역
Tracer c = makeMovedReturn();- 지역
t("C")생성(ctor C).return std::move(t)는 xvalue 반환이라 NRVO 대상이 아니다 → 반환 객체를t로부터 이동 생성(move C). 그 뒤t소멸: name 이 이동돼 비었으므로dtor(공백).c는 이동된 객체("C").
- 지역
- main 종료: 역순 소멸
dtor C(c) →dtor B(b) →dtor A(a).
참고: "moved-from" 객체는 여전히 소멸된다. std::string 은 이동 후 유효하지만 미지정 상태 (대개 빈 문자열)라
dtor의 name 이 빈 칸으로 보인다.
흔한 오해·함정
- "return std::move(x) 가 빠르다"는 오해: 지역 반환은 어차피 이동(또는 elision)이다.
std::move를 붙이면 보장된 NRVO/elision 을 꺼서 강제로 이동을 만들고, elision 됐을 경우보다 느려진다(pessimizing move). 컴파일러 경고-Wpessimizing-move가 잡아준다. - "이동은 항상 일어난다"는 오해: C++17 prvalue 반환은 이동조차 없다(객체가 애초에 하나).
- 이동 생성자에
noexcept: 컨테이너 재할당 시 이동을 쓰려면noexcept가 중요 (아니면 강한 예외 보장을 위해 복사로 폴백). 게임서버 핫패스 컨테이너에서 체감. - NRVO 비보장: 멀티 return/조건부 return 은 NRVO 가 깨질 수 있다. "한 변수만, 단일 경로로 반환"이 elision 친화적.
면접 포인트
- C++17 의 "보장된 복사 생략"과 NRVO(선택적)의 차이, 그리고
return std::move(local)이 왜 안티패턴인지. - return 시 지역이 rvalue 로 먼저 취급되는 규칙(이동 우선) — elision 이 안 돼도 복사가 아닌 이유.
- 이동 생성자
noexcept가std::vector재할당/push_back성능에 주는 영향.