Skip to main content
  1. Posts/

C++ std::move

·2 mins

std::move #

It really is just a cast to T&&

Some code #

#include <iostream>
#include <string>

class Widget {
public:
    Widget() = default;

    void set(const std::string & name_) {
        std::cout << "const ref " << name_ << std::endl;
        name = name_;
        std::cout << "const ref " << name << std::endl;

    }

    void set(std::string && name_) {
        std::cout << "r-ref " << name_ << std::endl;
        name = std::move(name_);
        std::cout << "r-ref " << name << std::endl;

    }
private:
    std::string name;
};

int main() {
    auto w = Widget{};
    w.set(std::string{"first"});
    std::string other { "other"};
    w.set(other);
    w.set(std::move(other));
    std::cout << "Other = '" << other << "'\n";
    return 0;
}

Output:

r-ref first
r-ref first
const ref other
const ref other
r-ref other
r-ref other
Other = ''

Observations #

If I change the second set method to:

    void set(std::string && name_) {
        std::cout << "r-ref " << name_ << std::endl;
        name = name_;
        std::cout << "r-ref " << name << std::endl;
    }

we get:

r-ref first
r-ref first
const ref other
const ref other
r-ref other
r-ref other
Other = 'other'

If we make other const:

int main() {
    auto w = Widget{};
    w.set(std::string{"first"});
    const std::string other { "other"};
    w.set(other);
    w.set(std::move(other));
    std::cout << "Other = '" << other << "'\n";
    return 0;
}

we get:

r-ref first
r-ref first
const ref other
const ref other
const ref other
const ref other
Other = 'other'

and clang-tidy, if installed, warns:

Clang-Tidy: Std::move of the const variable ‘other’ has no effect; remove std::move() or make the variable non-const

because, under standard C++ lookup rules, it cannot find a method with the signature void set(const std::string&& name_); so it defers to the const reference version, but if we add such a method:

    void set(const std::string && name_) {
        std::cout << "const r-ref " << name_ << std::endl;
        name = std::move(name_);
        std::cout << "const r-ref " << name << std::endl;
    }

Which works, sort of:

r-ref first
r-ref first
const ref other
const ref other
const r-ref other
const r-ref other
Other = 'other'

Note the last line, other has not been moved by std::string. That’s because std::string does not have any members that take a const T&&.

In fact, moving implies modification, so it makes little sense to create methods that take const T&&.

More links here:

Moving #

We ought to use std::exchange to safely move, and also ‘invalidate’ the moved-from object with a sensible value:

class Widget {
    Gizmo* gizmo_{};
    Value value_{};
public:
    Widget(Widget&& other) 
        : gizmo_(std::exchange(other.gizmo_, nullptr)), 
          value_(std::exchange(other.value_, {})) {}]
};