Поможет ли std::move() при построении объекта в операторе return или предотвратит RVO?
Из-за широкого спектра ответов от сообщества, я спрашиваю об этом в надежде разоблачить специфичные для реализации ответы от пользователей переполнения стека.
Какой из них является наилучшим (предлагает наибольшую оптимизацию)?
// version 1
MyObject Widget::GetSomething() {
return MyObject();
}
// version 2
MyObject Widget::GetSomething() {
return std::move(MyObject());
}
// version 3
MyObject Widget::GetSomething() {
auto obj = MyObject()
return obj;
}
// version 4
MyObject Widget::GetSomething() {
auto obj = MyObject()
return std::move(obj);
}
РЕДАКТИРОВАТЬ: Спасибо Якк, за прямой, уважительный ответ. [принятый ответ]
1 ответ
// version 1
MyObject Widget::GetSomething() {
return MyObject();
}
В C++03 это требует MyObject
копируемым. Во время выполнения никакая копия не будет сделана с использованием какого-либо "реального" компилятора с разумными настройками, как это разрешено стандартом.
В C++11 или 14 требуется, чтобы объект был подвижным или копируемым. Elision остается; перемещение или копирование не выполняется.
В C++ 17 здесь нет перемещения или копирования в elide.
В каждом случае на практике MyObject
напрямую создается в возвращаемом значении.
// version 2
MyObject Widget::GetSomething() {
return std::move(MyObject());
}
Это недопустимо в C++03.
В C++11 и выше, MyObject
перемещается в возвращаемое значение. Перемещение должно происходить во время выполнения (исключая как будто устранение).
// version 3
MyObject Widget::GetSomething() {
auto obj = MyObject();
return obj;
}
Идентичен версии 1, за исключением того, что C++ 17 ведет себя как C++11/14. Кроме того, выбор здесь более хрупкий; казалось бы, безобидные изменения могут заставить компилятор действительно двигаться obj
,
Теоретически 2 хода опущены здесь в C++11/14/17 (и 2 копии в C++03). Первая элита безопасна, вторая хрупка.
// version 4
MyObject Widget::GetSomething() {
auto obj = MyObject();
return std::move(obj);
}
На практике это ведет себя так же, как версия 2. Дополнительный ход (копия в C++03) происходит при создании obj
но он исключен, поэтому во время выполнения ничего не происходит.
Elision позволяет устранить побочные эффекты копирования / перемещения; время жизни объектов объединяется в один объект, и перемещение / копирование исключается. Конструктор все еще должен существовать, он просто никогда не вызывается.
Ответ
И 1, и 3 будут компилироваться в идентичный код времени выполнения. 3 немного более хрупкий.
И 2, и 4 компилируются в идентичный код времени выполнения. Он никогда не должен быть быстрее 1/3, но если перемещение может быть устранено компилятором, доказавшим, что это не выполняется, то же самое, что и при его выполнении, он может скомпилировать тот же код времени выполнения, что и 1/3. Это далеко не гарантировано и чрезвычайно хрупко.
Так 1>=3>=2>=4
на порядок быстрее или медленнее на практике, где "более хрупкий" код, который в остальном имеет ту же скорость, <=
,
В качестве примера случая, который может сделать 3 медленнее, чем 1, если у вас был оператор if:
// version 3 - modified
MyObject Widget::GetSomething() {
auto obj = MyObject();
if (err()) return MyObject("err");
return obj;
}
вдруг многие компиляторы будут вынуждены двигаться obj
в возвращаемое значение вместо исключения obj
и возвращаемое значение вместе.