Когда следует использовать std:: move для возвращаемого значения функции? [дубликат]



этот вопрос уже есть ответ здесь:




  • в C++11 оптимизация возвращаемого значения или двигаться? [дубликат]

    4 ответы



В этом случае



struct Foo {};
Foo meh() {
return std::move(Foo());
}


я почти уверен, что этот шаг не нужен, потому что вновь созданный Foo будет xvalue.



а что в таких случаях, как эти?



struct Foo {};
Foo meh() {
Foo foo;
//do something, but knowing that foo can safely be disposed of
//but does the compiler necessarily know it?
//we may have references/pointers to foo. how could the compiler know?
return std::move(foo); //so here the move is needed, right?
}


там переезд нужен, я полагаю?

609   6  

6 ответов:

в случае return std::move(foo); the move - это лишнее, из-за 12.8/32:

когда критерии для выбора операции копирования выполнены или будут встретил за исключением того, что исходный объект является параметром функции , и объект будет скопирован обозначается lvalue-выражение, перегрузки резолюции для выбора конструктора для копирования сначала выполняется, как если объект был обозначен rvalue.

return foo; это случай NRVO, так копию Элизии допускается. foo является lvalue. Итак, конструктор выбран для "копирования" из foo возвращаемое значение meh требуется быть конструктором перемещения, если он существует.

добавлять move имеет потенциальный эффект, хотя: это предотвращает перемещение элид, потому что return std::move(foo); и не право на NRVO.

насколько я знаю, 12.8/32 раскладывает только условия, при которых копия из значения lvalue может быть заменили на переезд. Компилятору вообще не разрешается обнаруживать, что значение lvalue не используется после копирования (например, с помощью DFA), и вносить изменения по собственной инициативе. Я предполагаю, что между ними есть заметная разница - если наблюдаемое поведение одинаково, то применяется правило "как-если".

Итак, чтобы ответить на вопрос в заголовке, используйте std::move на возвращаемое значение, когда вы хотите, чтобы он был перемещен, и он не будет перемещен в любом случае. Что это:

  • вы хотите, чтобы он двигался, и
  • это lvalue, и
  • он не имеет права на копирование elision, и
  • это не имя параметра функции по значению.

учитывая, что это довольно неудобно и движется обычно дешево, вы можете сказать, что в коде без шаблона вы можете немного упростить это. Используйте std::move когда:

  • вы хотите, чтобы он двигался, и
  • это lvalue, и
  • вы не можете беспокоиться об этом.

следуя упрощенным правилам, вы жертвуете некоторым движением elision. Для таких типов, как std::vector это дешево, чтобы двигаться вы, вероятно, никогда не заметите (и если вы заметите, вы можете оптимизировать). Для таких типов, как std::array это дорого для перемещения, или для шаблонов, где вы понятия не имеете, являются ли движения дешевыми или нет, вы, скорее всего, будете беспокоиться о том, что оно.

перемещение не требуется в обоих случаях. Во втором случае, std::move является излишним, потому что вы возвращаете локальную переменную по значению, и компилятор поймет, что, поскольку вы больше не собираетесь использовать эту локальную переменную, ее можно переместить, а не копировать.

в возвращаемом значении, если возвращаемое выражение ссылается непосредственно на имя локального значения lvalue (т. е. в этот момент xvalue), нет необходимости в std::move. С другой стороны, если возвращаемое выражение не идентификатор, он не будет перемещен автоматически, так что, например, вам понадобится явный std::move в этом случае:

T foo(bool which) {
   T a = ..., b = ...;
   return std::move(which? a : b);
   // alternatively: return which? std::move(a), std::move(b);
}

при прямом возврате именованной локальной переменной или временного выражения следует избегать явного std::move. Этот компилятор должны (и в будущем) двигаться автоматически в тех случаях, и добавив std::move может повлиять на другие оптимизации.

есть много ответов о том, когда он не должен быть перемещен, но вопрос в том, "когда он должен быть перемещен?"

вот надуманный пример того, когда его следует использовать:

std::vector<int> append(std::vector<int>&& v, int x) {
  v.push_back(x);
  return std::move(v);
}

ie, когда у вас есть функция, которая принимает ссылку rvalue, изменяет ее, а затем возвращает ее копию. Сейчас, на практике, эта конструкция почти всегда лучше:

std::vector<int> append(std::vector<int> v, int x) {
  v.push_back(x);
  return v;
}

что также позволяет принимать параметры, отличные от rvalue.

в принципе, если у вас есть ссылка rvalue в функции, которую вы хотите вернуть путем перемещения, вы должны вызвать std::move. Если у вас есть локальная переменная (будь то параметр или нет), возвращая его неявно moves (и это неявное движение может быть удалено, в то время как явное движение не может). Если у вас есть функция или операция, которая принимает локальные переменные и возвращает ссылку на указанную локальную переменную, вы должны std::move чтобы заставить движение произойти (например, тринарий ?: оператор).

компилятор C++ может свободно использовать std::move(foo):

  • если известно, что foo в конце своей жизни, и
  • неявное использование std::move не будет иметь никакого влияния на семантику кода C++, кроме семантических эффектов, разрешенных спецификацией C++.

это зависит от возможностей оптимизации компилятора C++, способен ли он вычислить, какие преобразования из f(foo); foo.~Foo(); до f(std::move(foo)); foo.~Foo(); выгодно с точки зрения производительности или с точки зрения потребления памяти, придерживаясь правил спецификации C++.


принципиально говоря, компиляторы C++ 2017 года, такие как GCC 6.3.0, являются возможность оптимизации этот код:

Foo meh() {
    Foo foo(args);
    foo.method(xyz);
    bar();
    return foo;
}

в этот код:

void meh(Foo *retval) {
   new (retval) Foo(arg);
   retval->method(xyz);
   bar();
}

что позволяет избежать вызова конструктора копирования и деструктора Foo.


год-2017 компиляторы C++, такие как GCC 6.3.0, являются не удается оптимизировать эти коды:

Foo meh_value() {
    Foo foo(args);
    Foo retval(foo);
    return retval;
}

Foo meh_pointer() {
    Foo *foo = get_foo();
    Foo retval(*foo);
    delete foo;
    return retval;
}

в эти коды:

Foo meh_value() {
    Foo foo(args);
    Foo retval(std::move(foo));
    return retval;
}

Foo meh_pointer() {
    Foo *foo = get_foo();
    Foo retval(std::move(*foo));
    delete foo;
    return retval;
}

это означает, что программисту 2017 года необходимо явно указать такие оптимизации.

std::move совершенно не нужно при возвращении из функции, и действительно попадает в область Вас-программист-пытается нянчить вещи, которые вы должны оставить компилятору.

что происходит, когда вы std::move что-то из функции, которая не является переменной локальные функции? Вы можете сказать, что никогда не напишете такой код, но что произойдет, если вы напишете код, который просто прекрасен, а затем рефакторируете его и рассеянно не меняете std::move. Вы будете иметь удовольствие отслеживать эту ошибку вниз.

компилятор, с другой стороны, в основном, не способна делать такие ошибки.

также важно отметить, что возвращение к локальной переменной из функции не обязательно создайте значение rvalue или используйте семантику перемещения.

см. здесь.

Comments

    Ничего не найдено.