Какова граница времени жизни скрытого массива, стоящего за std::initializer_list, если список создан как временный аргумент функции?
Скрытый массив за std::initializer_list, переданным как временный аргумент, живёт не дольше полного выражения, содержащего вызов функции. Он гарантированно существует во время выполнения функции, но после завершения вызова хранить или возвращать полученный std::initializer_list для последующего использования нельзя.
Копирование объекта std::initializer_list не копирует элементы и не продлевает время жизни скрытого массива: копии продолжают ссылаться на тот же временный массив.
std::initializer_list появился в C++11 вместе с единым синтаксисом инициализации. Он позволил конструкторам и функциям принимать последовательности элементов, записанные через фигурные скобки, без создания явного контейнера.
Для этого стандарт представляет список как лёгкий прокси-объект, содержащий ссылку на скрытый массив константных элементов и его размер. Такой подход дешёв по копированию, но сам по себе не предоставляет владения памятью.
Вызов функции с временным списком часто выглядит безопасно: функция получает корректный диапазон и может использовать его во время вызова. Ошибка возникает, когда она сохраняет std::initializer_list, возвращает его или передаёт его в объект, который переживает полный вызов.
После завершения полного выражения скрытый массив уничтожается. Последующее разыменование итератора или обращение к элементам становится обращением к недействительному объекту и приводит к неопределённому поведению.
При передаче временного списка функция получает объект std::initializer_list, ссылающийся на скрытый массив. Время жизни массива достаточно для выполнения тела функции, но не для использования результата после завершения выражения вызова.
Минимальный пример:
Возвращаемый список ссылается на массив, созданный внутри выражения return. После завершения этого выражения массив уничтожен, поэтому использование values в вызывающем коде небезопасно.
Локальная переменная имеет другую границу времени жизни: если std::initializer_list создаётся непосредственно в локальной области, скрытый массив обычно живёт до конца области видимости соответствующего объекта списка. Однако копирование списка всё равно лишь копирует пару представленных указателей и размер.
Практическое правило: std::initializer_list подходит для немедленного чтения элементов, но не для передачи владения или возврата последовательности. Если результат должен пережить вызов, нужно создать владеющий объект, например std::vector или std::array.
Есть и дополнительное ограничение: элементы списка имеют тип const, поэтому через std::initializer_list нельзя изменять элементы. Это делает интерфейс удобным для чтения, но неподходящим для алгоритмов, которым требуется модификация диапазона.
Фабрика должна вернуть набор начальных значений для последующей обработки. Разработчик выбирает один из трёх вариантов:
Для общего фабричного интерфейса выбран std::vector, поскольку результат должен жить независимо от функции и иметь переменный размер. std::initializer_list оставлен только для параметров, которые используются синхронно внутри вызова.
Нет. Копирование std::initializer_list обычно копирует только представление диапазона: указатель на первый элемент и размер. Все копии используют один и тот же скрытый массив, поэтому после его уничтожения недействительны все копии.
Только если сохранённый объект не используется после окончания полного выражения, в котором был вызван передающий код. Сам параметр может быть корректно прочитан внутри функции, но сохранение его в поле объекта или возврат наружу не продлевает жизнь скрытого массива.
std::initializer_list не владеет элементами и возвращает представление временного массива, поэтому после возврата часто становится висячим. std::vector владеет своим буфером и переносит владение в возвращаемый объект; его элементы остаются доступными в соответствии с временем жизни самого вектора.