목적

  • 복사 비용 최소화: 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도 활용 가능
  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의 경우 요소를 이동

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); }