Я отвечал на вопрос и рекомендует возвращать по значению для большого типа, потому что я был уверен, что компилятор выполнит оптимизация возврата (RVO). Но затем мне было указано, что Visual Studio 2013 не выполняет RVO в моем коде.
Я нашел здесь есть вопрос о том, что Visual Studio не может выполнить RVO, но в этом случае вывод, казалось, был таков, что если это действительно важно, Visual Studio выполнит RVO. В моем случае это имеет значение, оно оказывает значительное влияние на производительность, что я подтвердил результатами профилирования. Вот упрощенный код:
#include <vector>
#include <numeric>
#include <iostream>
struct Foo {
std::vector<double> v;
Foo(std::vector<double> _v) : v(std::move(_v)) {}
};
Foo getBigFoo() {
std::vector<double> v(1000000);
std::iota(v.begin(), v.end(), 0); // Fill vector with non-trivial data
return Foo(std::move(v)); // Expecting RVO to happen here.
}
int main() {
std::cout << "Press any key to start test...";
std::cin.ignore();
for (int i = 0; i != 100; ++i) { // Repeat test to get meaningful profiler results
auto foo = getBigFoo();
std::cout << std::accumulate(foo.v.begin(), foo.v.end(), 0.0) << "\n";
}
}
Я ожидаю, что компилятор выполнит RVO для возвращаемого типа из getBigFoo()
. Но, похоже, вместо этого он копирует Foo
.
Я знаю, что компилятор создаст конструктор копирования для Foo
. Я также знаю, что в отличие от совместимого компилятора C ++ 11 Visual Studio не создает конструктор перемещения для Foo
. Но это должно быть нормально, RVO - это концепция C ++ 98 и работает без семантики перемещения.
Итак, вопрос в том, есть ли веская причина, по которой Visual Studio 2013 не выполняет оптимизацию возвращаемого значения в этом случае?
Я знаю несколько обходных путей. Я могу определить конструктор ходов для Foo
:
Foo(Foo&& in) : v(std::move(in.v)) {}
и это нормально, но есть много устаревших типов, у которых нет конструкторов перемещения, и было бы неплохо узнать, что я могу положиться на RVO с этими типами. Кроме того, некоторые типы могут быть копируемыми, но не перемещаемыми.
Если я перейду с RVO на NVRO (названная оптимизация возвращаемого значения), тогда Visual Studio действительно выполнит оптимизацию:
Foo foo(std::move(v))
return foo;
что любопытно, потому что я думал, что NVRO менее надежен, чем RVO.
Еще более любопытно, если я изменю конструктор Foo
, чтобы он создавал и заполнял vector
:
Foo(size_t num) : v(num) {
std::iota(v.begin(), v.end(), 0); // Fill vector with non-trivial data
}
вместо того, чтобы перемещать его, когда я пытаюсь выполнить RVO, он работает:
Foo getBigFoo() {
return Foo(1000000);
}
Я рад воспользоваться одним из этих обходных путей, но я хотел бы иметь возможность предсказать, когда RVO может выйти из строя таким образом в будущем, спасибо.
Изменить: более краткое живое демо от @dyp
Edit2: Почему бы мне просто не написать return v;
?
Во-первых, это не помогает. Результаты профилировщика показывают, что Visual Studio 2013 по-прежнему копирует вектор, если я просто напишу return v;
. И даже если бы это сработало, это было бы только обходным путем. Я не пытаюсь исправить этот конкретный фрагмент кода, я пытаюсь понять, почему RVO выходит из строя, чтобы я мог предсказать, когда он может выйти из строя в будущем. Это правда, что это более сжатый способ написания этого конкретного примера, но есть много случаев, когда я не мог просто написать return v;
, например, если Foo
имел дополнительные параметры конструктора.
return Foo(std::move(v));
(круглые скобки) вместоreturn Foo{std::move(v)};
(фигурные скобки)? Я не думаю, что это будет иметь значение, но я не собираюсь на это делать ставку. - person In silico   schedule 22.09.2014return {std::move(v)};
, поскольку этот конструктор не является явным. Для этого не требуется никакого (N) RVO, это указано, чтобы не создавать временный. - person dyp   schedule 22.09.2014return {move(v)};
- person dyp   schedule 22.09.2014return v;
? - person Marc Glisse   schedule 22.09.2014fail
?std::move
не предотвращает копирование / перемещение исключения копирования / перемещенияFoo
временного значения в возвращаемое значение функции. - person dyp   schedule 21.10.2014Foo(vec_t && _v) : v(std::move(_v)) {}
не приводит к сбою, но я напортачил. Скорее всего, живой пример сейчас верен. - person Michael Karcher   schedule 21.10.2014vector
. Может быть, это основная проблема. (Но я не могу найти никаких ссылок в банкомате.) - person dyp   schedule 21.10.2014return
? - person Lightness Races in Orbit   schedule 09.11.2014struct : bar {} foo {};
означает для компилятора C ++, может быть щепетильным по поводу врожденной запутанности и нестабильности ее любимого языка. - person kuroi neko   schedule 11.12.2014