Когда компилятор C++11 заставит RVO и NRVO превзойти семантику перемещения и привязку константной ссылки?

Рассмотрим случай, когда из функций возвращаются «целые» объекты с включенной семантикой перемещения, как в случае с std::basic_string<>:

std::wstring build_report() const
{
    std::wstring report;
    ...

    return report;
}

Могу ли я тогда реально ожидать, что сделаю «лучший» выбор, использовать ли возвращаемую строку с семантикой перемещения, как в

const std::wstring report(std::move(build_report()));

или если я должен полагаться на (N)RVO, чтобы иметь место с

const std::wstring report(build_report());

или даже привязать константную ссылку к временному с помощью

const std::wstring& report(build_report());

Какая существует схема для детерминированного выбора этих вариантов, если таковые имеются?

РЕДАКТИРОВАТЬ 1: Обратите внимание, что использование std::wstring выше — это всего лишь пример типа с включенной семантикой перемещения. Его с таким же успехом можно поменять на свой arbitrary_large_structure. :-)

EDIT 2: я проверил сгенерированную сборку при запуске оптимизированной по скорости сборки выпуска в VS 2010 из следующего:

std::wstring build_report(const std::wstring& title, const std::wstring& content)
{
    std::wstring report;
    report.append(title);
    report.append(content);

    return report;
}

const std::wstring title1(L"title1");
const std::wstring content1(L"content1");

const std::wstring title2(L"title2");
const std::wstring content2(L"content2");

const std::wstring title3(L"title3");
const std::wstring content3(L"content3");

int _tmain(int argc, _TCHAR* argv[])
{
    const std::wstring  report1(std::move(build_report(title1, content1)));
    const std::wstring  report2(build_report(title2, content2));
    const std::wstring& report3(build_report(title3, content3));

    ...

    return 0;
}

2 самых интересных исхода:

  • Явный вызов std::move для report1 для использования конструктора перемещения утроивает количество инструкций.
  • Как отметил Джеймс Макнеллис в его ответ ниже, report2 и report3 действительно генерируют идентичные сборки с в 3 раза меньшим количеством инструкций, чем при явном вызове std::move.

person Johann Gerell    schedule 30.06.2011    source источник
comment
Я думаю, действительно странно, что вызов move не встроен и не удален.   -  person James McNellis    schedule 30.06.2011
comment
@James: Да, особенно в свете комментария Керрека С.Б. о том, что STL сказал, что RVO происходит раньше чего-либо еще. (stackoverflow.com/questions/6531700/) Вы случайно не находитесь рядом с STL, чтобы вы могли пройти и Спроси его? ;-)   -  person Johann Gerell    schedule 30.06.2011
comment
возможный дубликат C++0x rvalue и путаница с семантикой перемещения   -  person Howard Hinnant    schedule 30.06.2011
comment
@Howard: Хотя ваш ответ на этот вопрос очень хорош, я думаю, что этот вопрос затрагивает более глубокую проблему.   -  person Johann Gerell    schedule 30.06.2011
comment
Я провожу довольно много времени в здании, где он работает, да :-) Хотя с самим мистером СТЛ я еще не встречался. Однако я свяжусь с ними в отношении того, почему std::move здесь не встроен; Я действительно нахожу это озадачивающим. Я в отпуске до конца следующей недели, так что пройдет пара недель, прежде чем я смогу вернуться к вам.   -  person James McNellis    schedule 01.07.2011


Ответы (3)


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

Кроме того, когда вы возвращаете локальную переменную, она перемещается, если она относится к типу, имеющему конструктор перемещения, поэтому копии создаваться не будут, и точка.

Не должно быть никакой функциональной разницы между объявлением report как объекта или как константной ссылки; в обоих случаях вы получаете объект (либо именованный объект report, либо безымянный объект, к которому может быть привязана ссылка report).

person James McNellis    schedule 30.06.2011
comment
Два других должны быть функционально идентичны в присутствии (N)RVO - интересно. Я не думал об этом. Я посмотрю на сгенерированную сборку. Я вернусь! - person Johann Gerell; 30.06.2011
comment
Я немного отредактировал это: никогда не должно быть разницы между этими двумя: практически говоря, в обоих случаях у вас должен быть какой-то объект в области report; единственная разница заключается в том, является ли report именем объекта или ссылкой, привязанной к этому объекту... с точки зрения производительности между ними не должно быть никакой разницы. - person James McNellis; 30.06.2011
comment
См. мое Редактировать 2 в вопросе. - person Johann Gerell; 30.06.2011

Я не уверен, что это стандартизировано (как говорит Никол, вся оптимизация зависит от компилятора), но я слышал STL говорит об этом, и (по крайней мере, в MSVC) RVO происходит прежде чего-либо еще. Так что если есть возможность применить РВО, то это произойдет без каких-либо действий с вашей стороны. Во-вторых, когда вы возвращаете временное значение, вам не нужно писать std::move (я думаю, что это есть на самом деле в стандарте), так как возвращаемое значение будет неявно рассматриваться как rvalue.

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

person Kerrek SB    schedule 30.06.2011
comment
В моем Edit 2 видно, что явный вызов std::move даже полностью предотвратит (N)RVO. - person Johann Gerell; 30.06.2011
comment
@Йоханн: Круто. Я полагаю, это показывает, что RVO учитывается только в том случае, если вы возвращаете голый временный файл напрямую. Еще одна причина не пытаться перехитрить язык! - person Kerrek SB; 30.06.2011

Какова схема детерминированного выбора этих вариантов, если таковые имеются?

Его нет и никогда не будет.

Компиляторы не обязаны выполнять какую-либо оптимизацию. Единственное, что вы можете сделать наверняка, это скомпилировать код и посмотреть, что получится на другом конце.

Самое большее, что вы в конечном итоге получите, — это общая эвристика, консенсус сообщества, когда люди говорят: «Для большинства компиляторов X работает быстрее». Но это все. И на это уйдут годы, пока компиляторы осваивают C++0x, а реализации совершенствуются.

person Nicol Bolas    schedule 30.06.2011