[C++] std::move와 xvalue, 이동 생성자
0. value
C++에서 value는 크게 3가지로 분류된다.
분류 | 예시 | 설명 |
---|---|---|
lvalue |
int a = 5; , a , str |
메모리 주소를 가지는 객체 |
prvalue |
42 , "hello" , std::string("hi") |
임시 객체(pure rvalue) |
xvalue |
std::move(a) , std::string().substr(0,2) |
곧 소멸될 객체(expiring value), 이동 가능 대상 |
xvalue는 lvalue도 아니고 prvalue도 아닌, 리소스를 뺏어와도 되는 값이다. 임시 객체가 아니지만, 자원을 안전하게 옮겨도 괜찮은 상태를 의미한다.
1. &(lvalue reference) vs &&(rvalue reference)
문법 | 의미 | 사용 목적 |
---|---|---|
T& |
lvalue reference(좌측값 참조) | 수정 가능한 참조, 기존에 존재하는 값에 접근 |
T&& |
rvalue reference(우측값 참조) | 임시값이나 소멸 직전의 값에 참조하여 이동 가능하게 함 |
어떤 경우에 사용되는지 예시를 보자.
void foo(int& x)
{
std::cout << "lvalue reference\n";
}
void foo(int&& x)
{
std::cout << "rvalue reference\n";
}
int main()
{
int a = 10;
foo(a); //lvalue -> 출력 : lvalue reference
foo(10); // rvalue -> 출력 : rvalue reference
foo(std::move(a)) // xvalue -> 출력 : rvalue reference
return 0;
}
2. std::move
std::move
는 객체를 xvalue
로 캐스팅하여 이동 생성자/이동 대입 연산자가 호출되도록 유도한다.
내부적으로 static_cast<T&&>
를 수행한다.
예시를 보자.
#include <utility>
#include <string>
std::string a = "hello";
std::string b = a; // 복사
std::string c = std::move(a); // 이동
std::string b = a;
: b가 새 메모리를 할당 후, a의 문자열을 복사한다. 만약 더 이상 a가 필요 없는 경우, 메모리를 할당하고 값을 복사하는 오버헤드가 발생한다.std::string c = std::move(a)
: c가 a의 내부 포인터만 훔쳐오기 때문에 메모리를 할당할 필요가 없다. a의 내부 포인터는 nullptr 또는 빈 문자열로 초기화된다.
즉, std::vector
, std::string
, 사용자 정의 클래스 등 내부에 동적 메모리를 가지는 경우에서, std::move
를 사용하면 힙 메모리를 새로 할당/복사하지 않고 포인터와 사이즈 정보만 빠르게 옮기는 것이 가능하다.
이동 후 원본 객체는 “valid but unspecified state”, 즉 파괴해도 되지만 읽거나 쓰는 것은 피해야 하는 상태가 된다.
3. 이동 생성자(move constructor)
아래는 이동 생성자 예시 코드이다.
class Example
{
Example(Example&& other)
{
mArr = other->mArr;
other->mArr = nullptr;
}
private:
int* mArr;
}
other가 xvalue라면 새로운 인스턴스를 만들 때, mArr을 새로 할당하고 깊은 복사를 하는 것보다 other->mArr 포인터를 훔치는 것이 효율적이다. 인자로 rvalue reference가 들어오면 되는 것처럼 보이지만, 실제로 이동 생성자가 호출되려면 인자로 들어오는 표현식이 xvalue(ex. std::move(obj)
)여야 한다.
주의할 점
std::move
는 이동을 가능하게 해주는 것일 뿐, 진짜 이동이 일어나는지는 상황에 따라 다르다.
만약 이동 생성자가 없고 복사 생성자만 있다면, std::move
를 사용해도 결국 복사 생성자가 실행된다.
따라서 이동하려면 이동 생성자/연산자가 정의돼 있어야 한다. 또한 리소스를 다른 객체로 이동하고, 원래 객체를 더 이상 사용하지 않을 때 사용해야 한다.
Leave a comment