C++ std::move, 복사를 줄이는 이유
std::move는 인자를 rvalue 참조로 캐스트하는 유틸리티이며, 그 자체로는 자원을 옮기지 않습니다. 이동은 이동 생성자나 이동 대입처럼 rvalue를 받는 오버로드가 선택될 때 이뤄지며, 복사 대신 자원을 넘길 수 있습니다. 이 글은 cppreference의 이동 유틸리티와 이동 생성자, 값 범주 문서를 2026-08-27 기준으로 정리한 일반 설명이며, 표준 버전과 구현에 따라 세부 동작은 달라질 수 있습니다.
이 캐스트는 실제로 이동시키나?
한 줄 답: 이 유틸리티는 인자를 xvalue로 만드는 rvalue 참조 캐스트이며, rvalue 참조를 받는 오버로드가 선택될 때에만 자원 이전이 일어날 수 있습니다.
이 함수는 <utility>에 정의되며, 반환되는 식은 인자를 rvalue 참조형으로 static_cast한 것과 정확히 같습니다. 따라서 호출 자체가 문자열이나 컨테이너의 자원을 옮기는 연산은 아니며, 반환 식을 오버로드 해결에서 rvalue로 취급하게 하는 역할을 합니다.
이 결과를 인수로 전달하면 이동 생성자, 이동 대입 연산자, std::vector::push_back의 rvalue 참조 오버로드가 선택될 수 있습니다. 다만 선택된 함수도 자원을 반드시 옮기는 것은 아니며, 자원 소유 객체라면 옮길 수 있는 선택권을 가집니다.
복사와의 차이는 다음의 짧은 예시로 확인할 수 있습니다. 첫 번째 호출은 lvalue인 str을 push_back(const T&) 오버로드에 전달하고, 두 번째 호출은 xvalue를 push_back(T&&) 오버로드에 전달합니다.
std::string str = "Salut";
std::vector<std::string> v;
v.push_back(str);
v.push_back(std::move(str));
두 번째 호출에서는 문자열을 복사하는 대신 그 내용을 벡터로 넘길 수 있으므로, 복사해야 할 자원 소유 객체의 부담을 줄이는 방향으로 동작합니다. 호출 뒤 str이 빈 문자열처럼 보이는 결과는 가능한 결과 중 하나일 뿐 보장되지 않으며, 이동 뒤 상태를 특정 출력으로 단정할 수 없습니다.
rvalue와 값 범주는 무엇인가?
한 줄 답: 모든 식은 prvalue·xvalue·lvalue 중 정확히 하나의 값 범주를 가지며, rvalue는 prvalue 또는 xvalue입니다.
C++에서 식의 타입과 값 범주는 서로 독립적인 성질입니다. 모든 식은 비참조 타입을 가지며, prvalue·xvalue·lvalue 세 기본 값 범주 가운데 정확히 하나에 속합니다.
glvalue는 평가 결과가 객체나 함수의 식별성을 결정하는 식이며, lvalue는 xvalue가 아닌 glvalue입니다. xvalue는 자원을 재사용할 수 있는 객체를 나타내는 glvalue이고, rvalue는 prvalue와 xvalue를 묶은 이름입니다.
변수의 이름은 선언된 타입과 관계없이 lvalue입니다. 따라서 rvalue 참조 타입의 변수도 이름으로 쓰면 lvalue이며, rvalue 참조 매개변수나 변수의 이름을 rvalue 참조 오버로드에 전달할 때는 xvalue로 바꾸는 과정이 필요할 수 있습니다.
int a{42};
int&& r{std::move(a)};
// a is an lvalue
// std::move(a) is an xvalue
// r is an lvalue
rvalue를 함수 인수로 전달할 때 rvalue 참조 매개변수와 const lvalue 참조 매개변수를 받는 오버로드가 함께 있으면, 오버로드 해결은 rvalue 참조 쪽을 선택합니다. 이 선택이 같은 타입에서 이동 생성자와 복사 생성자를 구분하는 출발점이 됩니다.
이동 생성자는 언제 호출되나?
한 줄 답: 같은 타입의 rvalue로 초기화하고 이동 생성자가 제공되어 있으면 이동 생성자가 선택되며, lvalue이면 복사 생성자가 선택됩니다.
이동 생성자는 같은 클래스 타입의 인수로 호출할 수 있는 생성자이며, 첫 번째 매개변수는 T&&, const T&&, volatile T&& 또는 const volatile T&& 계열이어야 합니다. 다른 매개변수가 있다면 모두 기본 인수를 가져야 하며, 다음은 최소 형태의 예시입니다.
struct X
{
X(X&& other);
};
대표적인 호출은 같은 타입의 객체를 xvalue로 초기화하는 형태입니다. 복사 생성자와 이동 생성자가 모두 제공되어 있으면 rvalue 인수에는 이동 생성자가 선택되고, lvalue 인수에는 복사 생성자가 선택됩니다.
T a = std::move(b);
이동 생성자 내부에서 rvalue 참조 매개변수의 이름과 그 멤버 접근 식은 lvalue로 취급됩니다. 따라서 멤버를 다시 rvalue로 전달하려면 다음처럼 멤버 접근 식에 이 캐스트를 적용하는 형태가 흔히 사용됩니다.
A(A&& arg) : member(std::move(arg.member)) {}
이동 생성자를 직접 선언하지 않았고 복사 생성자, 복사 대입 연산자, 이동 대입 연산자, 소멸자도 사용자 선언하지 않았다면 컴파일러가 암시적 이동 생성자를 선언합니다. 사용자 선언 소멸자는 암시적 이동 생성을 막을 수 있으며, = default로 기본 동작을 명시할 수 있습니다.
이동한 뒤에는 객체를 어떻게 다루나?
한 줄 답: 표준 라이브러리 객체는 별도 규정이 없으면 이동 뒤에도 유효하지만 값은 미지정이므로, 전제조건을 확인한 뒤 사용해야 합니다.
“유효하지만 미지정 상태”라는 표현은 객체의 클래스 불변식은 유지되지만, 구체적인 값이나 내부 상태를 가정할 수 없다는 뜻입니다. 따라서 대입 연산처럼 전제조건이 없는 연산은 이동 뒤에도 사용할 수 있지만, 반환되는 값은 표준이 보장한 범위에서만 다뤄야 합니다.
문자열을 이동한 뒤 back()을 호출하려면 비어 있지 않다는 전제조건을 먼저 확인해야 합니다. 다음처럼 empty()로 조건을 확인한 뒤 back()을 호출하고, 전제조건이 없는 clear()를 사용할 수 있습니다.
v.push_back(std::move(str));
if (!str.empty())
str.back();
str.clear();
이동된 std::string이나 std::vector가 비어 있을 수는 있지만, 원본이 항상 비거나 이동 전 값을 그대로 유지한다고 단정할 수 없습니다. 표준 라이브러리 타입마다 별도 규정이 있으면 그 규정을 따라야 하며, 예를 들어 std::unique_ptr처럼 이동 후 상태가 완전히 규정된 타입도 있습니다.
이동 대입은 같은 타입의 rvalue를 인수로 받아 보통 자원을 이전하고, 인수를 유효하지만 그 밖에는 미지정 상태에 둘 수 있습니다. 표준 라이브러리 타입의 자기 이동 대입도 객체를 유효하지만 보통 미지정 상태에 둘 수 있으므로, 이동 전후의 특정 값을 전제로 작성하지 않아야 합니다.
FAQ
한 줄 답: 이 캐스트와 값 범주, 이동 생성자 선택, 이동 후 객체의 전제조건을 짧은 질문으로 다시 확인합니다.
이동과 복사를 구분할 때는 캐스트의 역할, 식의 값 범주, 이동 뒤 객체의 전제조건을 함께 확인해야 합니다. 다음 표는 앞의 내용을 짧게 다시 정리한 것입니다.
| 질문 | 답 |
|---|---|
| 이 유틸리티는 항상 자원을 옮깁니까? | 아닙니다. 이 유틸리티는 캐스트이며, 선택된 오버로드가 자원을 옮길 때 이전이 발생할 수 있습니다. |
rvalue 참조형 변수 r의 값 범주는 무엇입니까? | 타입은 rvalue 참조이지만 이름으로 쓴 식 r은 lvalue입니다. |
| 이동한 표준 라이브러리 객체는 어떻게 사용합니까? | 유효하지만 미지정 상태이므로 empty()처럼 전제조건이 없는 연산을 우선 확인하고, back()은 비어 있지 않은지 확인한 뒤 호출해야 합니다. |
| 이동 생성자가 없으면 무엇이 선택됩니까? | 복사 생성자만 제공된 경우에는 rvalue를 처리할 때 복사가 이동의 대체로 선택될 수 있습니다. |
관련 읽을거리는 C++ 기술면접 질문 10가지에서 확인할 수 있습니다.
출처
한 줄 답: 본문의 정의·값 범주·생성자·대입 상태는 아래 cppreference 문서를 2026-08-27 기준으로 정리한 내용입니다.
아래 문서는 캐스트와 xvalue, 값 범주, 이동 생성자 및 이동 대입의 조건과 이동 뒤 상태를 확인하는 공식 출처입니다. 문서 제목은 cppreference 원문 표기를 따릅니다.
- std::move —
<utility>의 이동 유틸리티, xvalue 변환, 오버로드 선택, 이동 후 표준 라이브러리 객체 상태를 설명합니다. - Value categories — prvalue·xvalue·lvalue와 glvalue·rvalue의 관계, rvalue 참조 변수 이름의 lvalue 성질을 설명합니다.
- Move constructors — 이동 생성자의 형태, 호출 조건, 암시적 선언 조건을 설명합니다.
- Move assignment operator — 이동 대입의 호출과 이동 후 인자 상태를 설명합니다.