Копирование разрешалось при ряде обстоятельств. Однако, даже если это было разрешено, код все равно должен был работать так, как если бы копия не была удалена. А именно, должен быть доступный конструктор копирования и / или перемещения.
Гарантированный копия элизия переопределяет ряд понятий , C ++, например , что некоторые обстоятельства , при которых копий / шаги могут быть опущены на самом деле не спровоцировать копирования / перемещения на всех . Компилятор не исключает копию; в стандарте сказано, что такого копирования никогда не может быть.
Рассмотрим эту функцию:
T Func() {return T();}
Согласно правилам негарантированной копии, это создаст временное, а затем перейдет из этого временного в возвращаемое значение функции. Эта операция перемещения может быть опущены, но T
все равно должны иметь доступный конструктор двигаться , даже если он никогда не используется.
По аналогии:
T t = Func();
Это инициализация копии t
. Это скопирует инициализацию t
с возвращаемым значением Func
. Однако T
конструктор перемещения по-прежнему должен быть, даже если он не будет вызываться.
Гарантированное исключение копирования переопределяет значение выражения prvalue . До C ++ 17 prvalue - временные объекты. В C ++ 17 выражение prvalue - это просто то, что может материализовать временное, но еще не временное.
Если вы используете prvalue для инициализации объекта типа prvalue, временное значение не материализуется. Когда вы это сделаете return T();
, это инициализирует возвращаемое значение функции через prvalue. Поскольку эта функция возвращается T
, временное не создается; инициализация prvalue просто напрямую инициирует возвращаемое значение.
Следует понимать, что, поскольку возвращаемое значение - это prvalue, это еще не объект . Это просто инициализатор для объекта, как и T()
есть.
Когда вы это сделаете T t = Func();
, prvalue возвращаемого значения непосредственно инициализирует объект t
; отсутствует этап «создание временного и копирование / перемещение». Поскольку Func()
возвращаемое значение является prvalue, эквивалентным T()
, t
инициализируется напрямую T()
, точно так же, как если бы вы это сделали T t = T()
.
Если prvalue используется каким-либо другим способом, prvalue материализует временный объект, который будет использоваться в этом выражении (или отброшен, если выражение отсутствует). Так что, если бы вы это сделали const T &rt = Func();
, prvalue материализовал бы временное значение (с использованием T()
в качестве инициализатора), в котором будет храниться ссылка rt
вместе с обычным временным материалом для продления срока жизни.
Одна вещь, которую позволяет вам делать гарантированная элизия, - это возвращать неподвижные объекты. Например, lock_guard
его нельзя скопировать или переместить, поэтому у вас не может быть функции, возвращающей его по значению. Но с гарантированной копией вы можете.
Гарантированное исключение также работает с прямой инициализацией:
new T(FactoryFunction());
Если FactoryFunction
возвращается T
по значению, это выражение не копирует возвращаемое значение в выделенную память. Вместо этого он будет выделять память и использовать выделенную память как память для возвращаемых значений напрямую для вызова функции.
Таким образом, фабричные функции, которые возвращаются по значению, могут напрямую инициализировать память, выделенную кучей, даже не зная об этом. Конечно, пока эти функции внутренне следуют правилам гарантированного исключения копий. Они должны возвращать prvalue типа T
.
Конечно, это тоже работает:
new auto(FactoryFunction());
Если вам не нравится писать имена типов.
Важно понимать, что вышеуказанные гарантии работают только для prvalues. То есть вы не получаете никакой гарантии при возврате именованной переменной:
T Func()
{
T t = ...;
...
return t;
}
В этом случае t
все еще должен быть доступный конструктор копирования / перемещения. Да, компилятор может выбрать оптимизацию копирования / перемещения. Но компилятор по-прежнему должен проверять наличие доступного конструктора копирования / перемещения.
Таким образом, для оптимизации именованного возвращаемого значения (NRVO) ничего не меняется.
std::function<T()>
.