On this page
article
C++ Move Semantics
목적
- 복사 비용 최소화: rvalue를 lvalue처럼 다루지 않고, 자원을 이동.
- ex) vector의 resize 등에서 효율적.
std::move
- lvalue를 rvalue로 캐스팅할 뿐, 자원의 이동은 발생하지 않음.
template<typename T>
constexpr std::remove_reference_t<T>&& move(T&& obj) noexcept {
return static_cast<std::remove_reference_t<T>&&>(obj);
}
Implement std::move
move를 다음과 같이 구현 시 move(o2)의 경우 forwarding reference 과정에서 move(Object& obj)로 전달되고, 그러면 복사 생성자가 불림.
template<typename T>
T&& move(T&& obj)
{
return static_cast<T&&>(obj);
}
int main()
{
Object o1, o2;
Object o3 = o1;
Object o4 = move(o2);
Object o5 = move(Object{});
}
그래서 다음과 같이 T가 가지고 있는 reference 모두 제거 후 다시 && 붙이는 방식으로 구현 시 move를 구현 가능하다.
constexpr std::remove_reference_t<T>&& move(T&&obj) noexcept
{
return static_cast<std::remove_reference_t<T>&&>(obj);
}
Implement move operation
- string같은 것은 std::move를 활용
- data의 경우 exchange를 활용하여 0으로 세팅
Foo& operator=(Foo other) // note: argument passed
by value
{
std::swap(data, other.data);
return *this;
}
[] Copy-and-swap Idiom| extratype
noexcept 필요성
- 문제상황
- vector resize를 해보면 이동 생성자가 아닌 복사 생성자 불림
- move를 사용하다 도중에 예외가 발생하면 resize 이전으로 돌아갈 수 없기때문
- vector는 move_if_noexcept라는 예외가 없으면 move, 있으면 copy하는 함수 사용하니까 noexcept붙이면 move로 대응 가능
- 대안
- 이동 생성자를 만들 때는 예외가 없도록 구현하고, noexcept를 붙임
- 템플릿 타입이 있을 때도 noexcept를 보장할 수 있다
- 방법은 아래와 같되 noexcept의 활용부터 파악하자면 아래와 같다
- noexcept operator: noexcept(expression)으로 예외 가능성이 있는지 조사
- noexcept specifier: 함수의 예외 여부를 알리는 용도
- std::is_nothrow_move_constructible_v
도 활용 가능
- 방법은 아래와 같되 noexcept의 활용부터 파악하자면 아래와 같다
class Object{
int n;
std::string s;
T t;
Object(Object&& other) noexcept(noexcept(t(std::move(other.t))) || std::is_nothrow_move_constructible_v<T>)
: n(other.n), s (std::move(other.s)), t(std::move(other.t))
}
- std::is_nothrow_move_constructible_v
- 이건 T가 nothrow move constructible인지, 즉 move constructor가 무조건 예외를 던지지 않는지를 판단하는 trait.
Array & move
- move 이전
- 원래 배열은 복사 및 이동 불가
- move로 인한 변경점
- 객체의 멤버로 배열이 있다면 객체 자체를 move하면 배열의 요소가 move
- vector vs array
- 멤버가 int
- vector의 경우 메모리가 바뀜
- array의 경우 요소 복사
- 멤버가 string
- vecotr의 경우 메모리가 바뀜
- array의 경우 요소를 이동
- 멤버가 int
Setter with move
- move 버전의 Setter를 제공해주기 위해선 forwarding reference로 모든 걸 만족시킬 수 있다.
struct Object {
String name;
template<typename T>
void set(T&& n) {
name = std::forward<T>(n);
}
};
Getter with move
- lvalue/rvalue 구분해서 getter 제공.
String& get() & { return data; }
String&& get() && { return std::move(data); }