Класс должен иметь действительный экземпляр или механизм перемещения для любого из этих синтаксисов, чтобы быть законным:
C x = factory();
C y( factory() );
C z{ factory() };
В С++ 03 было довольно распространено полагаться на copy elision, чтобы компилятор не касался конструктора копирования. Каждый класс имеет допустимую подпись конструктора копирования, независимо от того, существует ли определение.
В С++ 11 тип, не подлежащий копированию, должен определять C( C const & ) = delete;
, при этом любая ссылка на функцию недействительна независимо от использования (такая же для непередвижного). (С++ 11 §8.4.3/2). GCC, например, будет жаловаться при попытке вернуть такой объект по стоимости. Копирование elision перестает помогать.
К счастью, у нас также есть новый синтаксис для выражения намерения вместо того, чтобы полагаться на лазейку. Функция factory
может возвращать скопированный-init-list, чтобы построить результат на месте:
C factory() {
return { arg1, 2, "arg3" }; // calls C::C( whatever ), no copy
}
Изменить: Если есть сомнения, этот оператор return
анализируется следующим образом:
- 6.6.3/2: "Оператор return с бин-init-list инициализирует объект или ссылку, которые должны быть возвращены из функции путем копирования-списка-инициализации (8.5.4) из указанного списка инициализаторов."
- 8.5.4/1: "инициализация списка в контексте инициализации копирования называется copy-list-initialization." ¶3: "если T - тип класса, рассматриваются конструкторы. Соответствующие конструкторы перечислены, а лучший выбирается с помощью разрешения перегрузки (13.3, 13.3.1.7)".
Не вводите в заблуждение инициализацию списка копий имени. 8.5:
13: Форма инициализации (с использованием круглых скобок или
=
) обычно несущественна, но имеет значение, когда инициализатор или инициализируемый объект имеет тип класса; Смотри ниже. Если инициализация объекта не имеют тип класса, список выражений в инициализаторе в скобках должен быть одним выражением.14: Инициализация, которая встречается в форме
T x = a;
а также при передаче аргументов, возврату функции, выбрасывании исключения (15.1), обработке исключения (15.3) и инициализации элемента агрегации (8.5.1) называется копирование-инициализация.
И инициализация копий, и ее альтернатива, прямая инициализация, всегда откладываются до инициализации списка, когда инициализатор является списком с привязкой-инициализацией. Семантический эффект при добавлении =
, который является одной из причин, когда инициализация списка неформально называется равномерной инициализацией.
Существуют различия: прямая инициализация может вызывать явный конструктор, в отличие от копирования-инициализации. Инициализация кода инициализирует временную и копирует ее для инициализации объекта при преобразовании.
Спецификация инициализации списка копий для операторов return { list }
просто определяет точный эквивалентный синтаксис как temp T = { list };
, где =
обозначает копирование-инициализацию. Это не сразу означает, что вызывается конструктор копирования.
- Завершить редактирование.
Результат функции можно затем принять в ссылку rvalue, чтобы предотвратить копирование временного в локальное:
C && x = factory(); // applies to other initialization syntax
Вопрос в том, как инициализировать нестатический член из функции factory, возвращающей не скопируемый, недвигаемый тип? Справочный трюк не работает, потому что элемент ссылки не продлевает время жизни временного.
Заметьте, я не рассматриваю агрегатную инициализацию. Речь идет об определении конструктора.