Копіювання елізії було дозволено здійснювати за ряду обставин. Однак, навіть якщо це було дозволено, код все одно повинен мати можливість працювати так, ніби копія не була видалена. А саме, повинен бути доступний конструктор копії та / або переміщення.
Гарантований копія елізія переопределяет ряд понять , C ++, наприклад , що деякі обставини , при яких копій / кроки можуть бути опущені на насправді не спровокувати копіювання / переміщення на всіх . Компілятор не видаляє копію; стандарт говорить, що такого копіювання ніколи не могло статися.
Розглянемо цю функцію:
T Func() {return T();}
Згідно з правилами негарантованого копіювання, це створює тимчасове, а потім переходить із цього тимчасового у повернене значення функції. Ця операція переміщення може бути вилучена, але вона T
все одно повинна мати доступний конструктор переміщення, навіть якщо вона ніколи не використовується.
Аналогічно:
T t = Func();
Це ініціалізація копії t
. Буде скопійовано ініціалізацію t
зі значенням, що повертається Func
. Однак, T
як і раніше, повинен бути конструктор переміщення, хоча він і не буде викликаний.
Гарантована копія елізії перевизначає значення виразу першого значення . Pre-C ++ 17, prvalues - це тимчасові об'єкти. У C ++ 17 вираз першого значення - це лише те, що може матеріалізуватися тимчасовим, але це ще не тимчасове.
Якщо ви використовуєте prvalue для ініціалізації об'єкта типу prvalue, тоді жодне тимчасове не матеріалізується. Коли ви це зробите return T();
, це ініціалізує повернене значення функції за допомогою першого значення. Оскільки ця функція повертається T
, тимчасові не створюються; ініціалізація першого значення просто безпосередньо ініціює повернене значення.
Потрібно зрозуміти, що оскільки повернене значення є першим значенням, воно ще не є об’єктом . Це просто ініціалізатор об’єкта, як і T()
є.
Коли ви це зробите T t = Func();
, перше значення поверненого значення безпосередньо ініціалізує об'єкт t
; немає етапу "створити тимчасовий і скопіювати / перемістити". Оскільки Func()
повертається значення '' - це перше значення, еквівалентне T()
, t
безпосередньо ініціалізується T()
, точно так само, як якщо б ви це зробили T t = T()
.
Якщо перше значення використовується будь-яким іншим способом, перше значення буде матеріалізувати тимчасовий об'єкт, який буде використаний у цьому виразі (або відкинутий, якщо відсутній вираз). Отже, якщо б ви це зробили const T &rt = Func();
, prvalue матеріалізував би тимчасовий (з використанням T()
як ініціалізатор), посилання якого буде зберігатися rt
разом із звичайними тимчасовими матеріалами продовження життя.
Гарантована елізія дозволяє зробити одне - повернути об’єкти, що не рухаються. Наприклад, lock_guard
не можна скопіювати або перемістити, тому у вас не може бути функції, яка повертає її за значенням. Але з гарантованим копіюванням ви можете.
Гарантована елісія також працює з прямою ініціалізацією:
new T(FactoryFunction());
Якщо FactoryFunction
повертається T
за значенням, цей вираз не копіює значення, що повертається, у виділену пам'ять. Натомість він виділить пам’ять і використає виділену пам’ять як пам’ять із зворотним значенням для безпосереднього виклику функції.
Тож заводські функції, які повертаються за значенням, можуть безпосередньо ініціалізувати купу виділеної пам'яті, навіть не знаючи про це. Поки ці функції внутрішньо відповідають правилам гарантованого копіювання, звичайно. Вони повинні повернути перше значення типу T
.
Звичайно, це теж працює:
new auto(FactoryFunction());
Якщо вам не подобається писати імена типів.
Важливо визнати, що вищезазначені гарантії працюють лише для цінностей. Тобто ви не отримуєте жодних гарантій при поверненні іменованої змінної:
T Func()
{
T t = ...;
...
return t;
}
У цьому випадку t
все одно повинен бути доступний конструктор копіювання / переміщення. Так, компілятор може вибрати оптимізацію копіювання / переміщення. Але компілятор все одно повинен перевірити наявність доступного конструктора копіювання / переміщення.
Отже, нічого не змінюється для оптимізованого іменованого значення (NRVO).