Подтвердить что ты не робот

Инициализация не скопируемого элемента (или другого объекта) на месте с помощью функции factory

Класс должен иметь действительный экземпляр или механизм перемещения для любого из этих синтаксисов, чтобы быть законным:

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, возвращающей не скопируемый, недвигаемый тип? Справочный трюк не работает, потому что элемент ссылки не продлевает время жизни временного.

Заметьте, я не рассматриваю агрегатную инициализацию. Речь идет об определении конструктора.

4b9b3361

Ответ 1

По вашему основному вопросу:

Вопрос в том, как инициализировать нестатический член из функции factory, возвращающей не скопируемый, непередвигаемый тип?

Нет.

Ваша проблема заключается в том, что вы пытаетесь сконфигурировать две вещи: как генерируется возвращаемое значение и как возвращаемое значение используется на сайте вызова. Эти две вещи не связаны друг с другом. Помните: определение функции не может повлиять на ее использование (с точки зрения языка), поскольку это определение не обязательно доступно компилятору. Поэтому С++ не позволяет способ генерации возвращаемого значения для чего-либо (вне исключения, которое является оптимизацией, а не требованием к языку).

Другими словами, это:

C c = {...};

Отличается от этого:

C c = [&]() -> C {return {...};}()

У вас есть функция, которая возвращает тип по значению. Он возвращает выражение prvalue типа C. Если вы хотите сохранить это значение, присвоив ему имя, у вас есть ровно два варианта:

  • Сохраните его как const& или &&. Это продлит время жизни временного ресурса блока управления. Вы не можете сделать это с переменными-членами; это может быть сделано только с автоматическими переменными в функциях.

  • Скопируйте/переместите его в значение. Вы можете сделать это с помощью переменной-члена, но для этого, очевидно, требуется, чтобы тип был скопируемым или подвижным.

Это единственные опции, которые С++ предоставляет вам, если вы хотите сохранить выражение prvalue. Таким образом, вы можете либо сделать тип перемещаемым, либо вернуть только что выделенный указатель на память и сохранить его вместо значения.

Это ограничение - большая часть причины, по которой движение было создано в первую очередь: чтобы иметь возможность передавать вещи по стоимости и избегать дорогостоящих копий. Язык не может быть изменен для принудительного выравнивания возвращаемых значений. Поэтому вместо этого они во многих случаях уменьшали стоимость.

Ответ 2

Такие проблемы были среди основных мотивов для изменения в С++ 17, чтобы разрешить эти инициализации (и исключить копии с языка, а не просто как оптимизацию).