Когда следует использовать 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?
}
там переезд нужен, я полагаю?
6 ответов:
в случае
return std::move(foo);themove- это лишнее, из-за 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