Предположим, у меня есть такая функция, как:
using std::vector;
vector<int> build_vector(int n)
{
if (some_condition(n)) return {};
vector<int> out;
for(int x : something())
{
out.push_back(x);
}
return out;
}
Предотвращает ли return {}
в начале функции NRVO? Мне любопытно, так как кажется, что это было бы эквивалентно следующему:
using std::vector;
vector<int> nrvo_friendly_build_vector(int n)
{
vector<int> out;
if (some_condition(n)) return out;
for(int x : something())
{
out.push_back(x);
}
return out;
}
Но мне было непонятно, разрешено ли компилятору выполнять NRVO в первом случае.
Правило as-if дает компилятору большую свободу действий. Например, он может превратить первую версию во вторую, поскольку это не меняет наблюдаемого поведения. Тем не менее, вам, вероятно, придется проверить сборку.
но не удалось превратить первое во второе, изменить наблюдаемое поведение, если было исключено копирование ctor с побочным эффектом, которого в противном случае не было бы?
@eljay не обязательно, мне больше любопытно, если это то, о чем я должен заботиться на практике
Если конструктор вектора имеет побочный эффект, который потенциально может быть обнаружен some_condition
, то две версии кода не идентичны. Я не могу придумать какого-либо наблюдаемого побочного эффекта, который может вызвать создание экземпляра вектора в автоматической области видимости. В динамической области, возможно, с вашим собственным распределителем. Но не в автоматическом режиме.
@GordonBailey Если some_condition
полагался на это, то, вероятно, это было бы недопустимо. Если этого не произошло, то его можно оптимизировать. Поскольку разрешены RVO / NRVO, компилятор может отказаться от копирования / перемещения, даже если это имеет побочные эффекты.
@GordonBailey Мне действительно было интересно то же самое, (тем более, что копирование не "требуется" стандартом), поэтому я быстро протестировал его в онлайн-компиляторе, заменив std::vector
структурой Widget
: V1 (build_vector
) coliru.stacked-crooked.com/a/5e55efe46bfe32f5 и V2 (nrvo_friendly_build_vector
) coliru.stacked-crooked.com/a/51b036c66e993d62. Как вы можете видеть, в этом конкретном случае (struct
не видит побочных эффектов от создания some_condition
), V1 вызывает конструктор перемещения, если some_condition
имеет значение false (по крайней мере, в clang и g ++). HTH
@ 865719 ох уж интересно! Похоже, что даже с -O3
есть дополнительный вызов ctor и dtor! Можете ли вы дать ответ, чтобы я принял его? coliru.stacked-crooked.com/a/37e9cd5558c16536coliru.stacked-crooked.com/a/b79474463cc8ae44
От https://en.cppreference.com/w/cpp/language/copy_elision
Under the following circumstances, the compilers are permitted, but not required to omit the copy and move (since C++11) construction of class objects even if the copy/move (since C++11) constructor and the destructor have observable side-effects. The objects are constructed directly into the storage where they would otherwise be copied/moved to. This is an optimization: even when it takes place and the copy/move (since C++11) constructor is not called, it still must be present and accessible (as if no optimization happened at all), otherwise the program is ill-formed:
In a return statement, when the operand is the name of a non-volatile object with automatic storage duration, which isn't a function parameter or a catch clause parameter, and which is of the same class type (ignoring cv-qualification) as the function return type. This variant of copy elision is known as NRVO, "named return value optimization".
...
Ограничений по досрочному возврату нет, поэтому обе версии являются кандидатами в NRVO.
Как просили от ОП, вот адаптированная версия мой комментарий
Мне действительно было интересно то же самое, что и (тем более, что копирование не "требуется" стандартом), поэтому я быстро протестировал его в онлайн-компиляторе, заменив std::vector
структурой Widget:
struct Widget
{
int val = 0;
Widget() { printf("default ctor\n"); }
Widget(const Widget&) { printf("copy ctor\n"); }
Widget(Widget&&) { printf("move ctor\n"); }
Widget& operator=(const Widget&) { printf("copy assign\n"); return *this; }
Widget& operator=(Widget&&) { printf("move assign\n"); return *this; }
~Widget() { printf("dtor\n"); }
void method(int)
{
printf("method\n");
}
};
V1 с использованием build_vector()
: http://coliru.stacked-crooked.com/a/5e55efe46bfe32f5
#include <cstdio>
#include <array>
#include <cstdlib>
using std::array;
struct Widget
{
int val = 0;
Widget() { printf("default ctor\n"); }
Widget(const Widget&) { printf("copy ctor\n"); }
Widget(Widget&&) { printf("move ctor\n"); }
Widget& operator=(const Widget&) { printf("copy assign\n"); return *this; }
Widget& operator=(Widget&&) { printf("move assign\n"); return *this; }
~Widget() { printf("dtor\n"); }
void method(int)
{
printf("method\n");
}
};
bool some_condition(int x)
{
return (x % 2) == 0;
}
array<int, 3> something()
{
return {{1,2,3}};
}
Widget build_vector(int n)
{
if (some_condition(n)) return {};
Widget out;
for(int x : something())
{
out.method(x);
}
return out;
}
int main(int argc, char* argv[])
{
if (argc != 2)
{
return -1;
}
const int x = atoi(argv[1]);
printf("call build_vector\n");
Widget w = build_vector(x);
printf("end of call\n");
return w.val;
}
Выход V1
call build_vector
default ctor
method
method
method
move ctor
dtor
end of call
dtor
V2 с использованием nrvo_friendly_build_vector()
: http://coliru.stacked-crooked.com/a/51b036c66e993d62
#include <cstdio>
#include <array>
#include <cstdlib>
using std::array;
struct Widget
{
int val = 0;
Widget() { printf("default ctor\n"); }
Widget(const Widget&) { printf("copy ctor\n"); }
Widget(Widget&&) { printf("move ctor\n"); }
Widget& operator=(const Widget&) { printf("copy assign\n"); return *this; }
Widget& operator=(Widget&&) { printf("move assign\n"); return *this; }
~Widget() { printf("dtor\n"); }
void method(int)
{
printf("method\n");
}
};
bool some_condition(int x)
{
return (x % 2) == 0;
}
array<int, 3> something()
{
return {{1,2,3}};
}
Widget nrvo_friendly_build_vector(int n)
{
Widget out;
if (some_condition(n)) return out;
for(int x : something())
{
out.method(x);
}
return out;
}
int main(int argc, char* argv[])
{
if (argc != 2)
{
return -1;
}
const int x = atoi(argv[1]);
printf("call nrvo_friendly_build_vector\n");
Widget w = nrvo_friendly_build_vector(x);
printf("end of call\n");
return w.val;
}
Выход V2
call nrvo_friendly_build_vector
default ctor
method
method
method
end of call
dtor
Как видите, в этом конкретном случае (some_condition не видит побочных эффектов от создания структуры), V1 вызывает конструктор перемещения, если some_condition()
ложно (по крайней мере, в clang и gcc, используя -std=c++11
и -O2
, в Колиру)
Более того, как и ты заметил, похоже, что то же самое происходит и с -O3
.
HTH
ps: Изучая возможность копирования, вы можете найти Журнал Abseil # 11 интересным;)
Вероятно, следует упомянуть, что C++ 17 вводит «гарантированное исключение копирования». Однако мое (базовое) понимание после (очень) быстрого просмотра jonasdevlieghere.com/guaranteed-copy-elision/… и stackoverflow.com/a/38043447/865719 состоит в том, что теперь можно иметь возможность копирования для неподвижных типов (что позволяет возвращать по значению для этих типов), что, если я не ошибаюсь, не имеет отношения к варианту использования OP.
Вы ищете ответ языкового юриста, цитируя главу и стих из стандарта?