50. std::move 의 정체와 const 객체의 "이동"
난이도 상내 리뷰 · C++
해설 · C++
해설 — std::move 의 정체와 const 객체의 "이동"
난이도: 상
요약
std::move(x) 는 아무것도 옮기지 않는다. x 를 rvalue(정확히는 xvalue)로 캐스팅할
뿐이다. 실제 이동은 그 결과를 받는 생성자/대입 오버로드 선택에서 일어난다.
const 객체에 std::move 를 걸면 결과 타입이 const W&& 가 되어 이동 생성자
W(W&&)(비-const 우변참조) 에 바인딩되지 못하고, 복사 생성자 W(const W&) 가
선택된다 — 즉 조용히 복사된다.
실행 결과(예측)
ctor a
ctor b
-- move non-const --
MOVE a
a.tag after move = []
-- move const --
COPY b
b.tag after move = [b]
-- done --
핵심 개념
- std::move 는 캐스트다. 개념적으로
static_cast<remove_reference_t<T>&&>(x). 런타임 동작이 없고, 값 범주(value category)만 lvalue → xvalue 로 바꾼다. - 오버로드 해석이 이동/복사를 결정한다.
W&&파라미터는 우변값(및 xvalue)에 바인딩되고,const W&는 거의 모든 것에 바인딩된다. 이동 생성자가 선택되어야 비로소 자원(여기서는std::string버퍼)이 옮겨진다.
단계별 내부 동작
W c = std::move(a);std::move(a)의 타입은W&&(비-const xvalue).- 오버로드 후보
W(W&&)와W(const W&)중W&&가 더 정확히 일치 → 이동 생성자. std::string의 이동으로a.tag의 내부 버퍼가c.tag로 이전 →a.tag는 "유효하지만 미규정(valid but unspecified)" 상태, 표준 라이브러리 구현상 보통 빈 문자열. 그래서a.tag after move = [].
W d = std::move(b);(b는const W)std::move(b)의 타입은const W&&.W(W&&)파라미터는const를 버릴 수 없어const W&&를 받지 못한다.- 남은 후보
W(const W&)가const W&&→const W&로 바인딩 가능 → 복사 생성자. - 복사이므로
b.tag는 그대로 남아b.tag after move = [b].
흔한 오해·함정
- "std::move 를 쓰면 무조건 이동된다" — 아니다. const 객체, 이동 생성자가 없는
타입,
const&&를 못 받는 경우 등에서는 복사로 폴백된다(그리고 조용하다). - 멤버가 const 라 클래스 전체 이동이 복사로 떨어지는 경우도 같은 원리.
return std::move(local);은 오히려 NRVO(복사 생략)를 방해할 수 있어 지역 반환에는 쓰지 않는 게 낫다(값 반환은 그대로 두면 컴파일러가 최적화).- moved-from 상태는 "유효하지만 미규정" — 재대입/
clear()후 재사용은 안전하나, 옮겨진 값이 특정 상태(예: 빈 문자열)라고 이식성 있게 가정하면 안 된다.
면접 포인트
- std::move 는 런타임 없는 캐스트이며, 이동은 오버로드 선택의 결과다.
const+std::move= 복사(그리고 경고 없음) — 성능 함정으로 자주 등장.- moved-from 객체의 계약("유효하지만 미규정")과 안전한 재사용 규칙.