Как правильно использовать семантику перемещения в C ++? - PullRequest
0 голосов
/ 16 сентября 2018

Рассмотрим этот код:

class A {
private:
    std::string data;
public:
    void set_data(std::string&& data) {
        this->data = std::move(data); // line 6
    }
};

int main() {
    std::string move_me = "Some string";
    A a;
    a.set_data(std::move(move_me)); // line 13
}

Я понимаю, что нам нужно вызвать std::move() в строке 13, чтобы она приводила ссылку lvalue к rvalue (это звучит правильно? Я новичок в этом).

Однако, в строке 6, нам нужно снова использовать std::move()? Я предполагаю, что нет, так как мы уже передали ссылку на rvalue и будет вызван конструктор перемещения std::string. Это правильно?

Ответы [ 3 ]

0 голосов
/ 16 сентября 2018

Однако в строке 6 нужно ли снова использовать std::move()?

Да .Зачем?Потому что внутри set_data, data (аргумент) является lvalue, потому что оно имеет имя .Оба std::move необходимы для фактического перемещения move_me в data в a.

Без std::move в строке 6, move_me не будет перемещено, потому что этозвоните std::string(const std::string&), а не std::string(std::string&&).

Помните - если что-то имеет имя , это lvalue.

0 голосов
/ 17 сентября 2018

Кажется, что оба ответа верны, я просто добавляю абзац из стандарта, который объясняет, почему правильно использовать std::move() в строке #6 и строке #13 и почему это lvalue, даже если тип является Значение в строке #6.

Тип выражения - это тип идентификатора. Результатом является объект, обозначенный идентификатором. Результатом является lvalue, если объект является функцией, переменной или элементом данных, и prvalue в противном случае. 5.1.1 [expr.prim.general] / 8

Таким образом, применяя это правило из стандарта, мы можем надеяться, что наши ответы будут правильными.

* именующие 1016 *

    // move_me is identifier of a variable denotes to itself the result is lvalue
    std::string move_me = "Some string";

Rvalue

   // constructing temporary e.g no  identifier is an rvalue
   std::string("Some string") ; 

* именующие 1026 *

  // the variable data has type rvalue reference to move_ms, it denotes entity move_ms
  // the result is lvalue
  void set_data(std::string&& data);

lvalue

// the variable data has type  lvalue reference to move_ms, 
//it denotes entity move_ms the result is lvalue
void set_data(std::string& data);

lvalue или rvalue - Универсальные ссылки

//the variable data has type universal reference it either holds lvalue or rvalue
template<typename T> void setdata(T && data) ;

Итак, ссылка на rvalue не является rvalue, все может пойти не так

Base(Base const & rhs); // non-move semantics
Base(Base&& rhs); // move semantics 

если вы не используете std :: move ()

 Derived(Derived&& rhs) : Base(rhs) // wrong: rhs is an lvalue
 {
  // Derived-specific stuff
 }

Правильная версия:

  Derived(Derived&& rhs) : Base(std::move(rhs)) // good, calls Base(Base&& rhs)
  {
  // Derived-specific stuff
  }

Также

  • создание ссылки lvalue на lvalue - ОК
  • создание ссылки на rvalue для rvalue - ОК
  • создание константы lvalue для ссылки на rvalue - ОК
  • создание ссылки lvalue на rvalue - ОШИБКА компиляции
0 голосов
/ 16 сентября 2018

Вам нужно это как в строке #6, так и в строке #13.

Существует хороший пост от Скотта Майерса на эту тему.

наиболее приемлемыми являются

// 1: full flexibility for the caller to decide where the data state comes from
struct X
{
    Y data_;
    explicit X(const Y& data) : data_(data) { }
    explicit X(Y&& data) : data_(std::move(data)) { }
};

// 2: forced copy or move at the call site and zero-copy move into the internal state of the X
struct X
{
    Y data_;
    explicit X(Y data) : data_(std::move(data)) { }
};

// 3: same as the setter below, but can have quite different forms based on what exactly is required
struct X
{
    Y data_;
    template <class... Z>
    explicit X(Z&&... arg) : data_(std::forward<Z>(args)...) { }
}

. Сеттер лучше всего выполнять в «прозрачном» стиле, эффективно делегируя оператору присваивания поля.

template <typename Arg> void setData(Arg&& arg) {
    data_ = std::forward<Arg>(arg);
}

Я бы порекомендовал написать простой код.класс со всеми видами конструкторов / операторов копирования / перемещения, оснащенных отладочными отпечатками, и немного поиграем с таким классом, чтобы развить интуицию о том, как работать с &&, std::forward и std::move.Во всяком случае, это то, что я делал в те дни.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...