Поможет ли 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 и возвращаемое значение вместе.

Другие вопросы по тегам