Я отвечал на вопрос и рекомендовал вернуть значение для большого типа, потому что я был уверен, что компилятор выполнит оптимизацию возвращаемого значения (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
. Я также знаю, что в отличие от совместимого компилятора С++ 11 Visual Studio не создает конструктор move для Foo
. Но это должно быть ОК, RVO является концепцией С++ 98 и работает без семантики перемещения.
Итак, вопрос в том, есть ли веская причина, почему Visual Studio 2013 не выполняет оптимизацию возвращаемого значения в этом случае?
Я знаю несколько обходных решений. Я могу определить конструктор move для Foo
:
Foo(Foo&& in) : v(std::move(in.v)) {}
что хорошо, но есть много устаревших типов, у которых нет конструкторов move, и было бы неплохо узнать, что я могу полагаться на RVO с этими типами. Кроме того, некоторые типы могут быть неотъемлемо скопируемыми, но не подвижными.
Если я перехожу из RVO в NVRO (с именем return value optimization), то 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
имел дополнительные параметры конструктора.