время жизни возвращаемого значения std :: initializer_list
Внедрение GCC уничтожаетstd::initializer_list
массив, возвращаемый функцией в конце возвращаемого полного выражения. Это верно?
В обоих тестовых примерах этой программы показаны деструкторы, выполняющиеся до того, как можно будет использовать значение:
#include <initializer_list>
#include <iostream>
struct noisydt {
~noisydt() { std::cout << "destroyed\n"; }
};
void receive( std::initializer_list< noisydt > il ) {
std::cout << "received\n";
}
std::initializer_list< noisydt > send() {
return { {}, {}, {} };
}
int main() {
receive( send() );
std::initializer_list< noisydt > && il = send();
receive( il );
}
Я думаю, что программа должна работать. Но основной стандарт немного запутан.
Оператор return инициализирует объект возвращаемого значения, как если бы он был объявлен
std::initializer_list< noisydt > ret = { {},{},{} };
Это инициализирует один временныйinitializer_list
и его базовый массив хранения из данной серии инициализаторов, а затем инициализирует другойinitializer_list
с первого. Каково время жизни массива? «Время жизни массива такое же, как уinitializer_list
объект. "Но есть два из них; один является неоднозначным. Пример в 8.5.4 / 6, если он работает как объявлено, должен разрешить неоднозначность, что у массива есть время жизни скопированного объекта. Тогда возврат Массив значения также должен сохраняться в вызывающей функции, и должна быть возможность сохранить его, связав его с именованной ссылкой.
НаLWS, GCC ошибочно убивает массив перед возвратом, но сохраняет именованныйinitializer_list
по примеру. Clang также правильно обрабатывает пример, но объекты в спискеникогда уничтожены; это может привести к утечке памяти. ICC не поддерживаетinitializer_list
вообще.
Мой анализ правильный?
C ++ 11 §6.6.3 / 2:
Заявление о возврате сприготовился-INIT-лист инициализирует объект или ссылку, которые должны быть возвращены из функции путем копирования-инициализации списка (8.5.4) из указанного списка инициализатора.
8.5.4 / 1:
… Инициализация списка в контексте инициализации копирования называетсякопирование списка инициализация.
8.5 / 14:
Инициализация, которая происходит в формеT x = a;
… называетсякопия инициализация.
Вернуться к 8.5.4 / 3:
Инициализация списка объекта или ссылки типа T определяется следующим образом:…
- В противном случае, если T является специализациейstd::initializer_list<E>
,initializer_list
Объект строится, как описано ниже, и используется для инициализации объекта в соответствии с правилами инициализации объекта из класса того же типа (8.5).
8.5.4 / 5:
Объект типаstd::initializer_list<E>
построен из списка инициализатора, как если бы реализация выделяла массивN элементы типаE, гдеN количество элементов в списке инициализатора Каждый элемент этого массива инициализируется копией с соответствующим элементом списка инициализатора, иstd::initializer_list<E>
Объект создается для ссылки на этот массив. Если для инициализации какого-либо элемента требуется сужающее преобразование, программа работает некорректно.
8.5.4 / 6:
Время жизни массива такое же, как уinitializer_list
объект.[Пример:
typedef std::complex<double> cmplx;
std::vector<cmplx> v1 = { 1, 2, 3 };
void f() {
std::vector<cmplx> v2{ 1, 2, 3 };
std::initializer_list<int> i3 = { 1, 2, 3 };
}
Заv1
а такжеv2
,initializer_list
объект и массив, созданные для{ 1, 2, 3 }
иметь полное выражение жизни. Заi3
, объект initializer_list и массив имеют автоматическое время жизни.- конец примера]
Когда вы возвращаете пустой список, заключенный в фигурные скобки,
Оператор return со списком фигурных скобок инициализирует объект или ссылку, которые должны быть возвращены из функции путем копирования-инициализации списка (8.5.4) из указанного списка инициализатора.
Это не означает, что объект, возвращаемый в область вызова, копируется из чего-либо. Например, это действительно:
struct nocopy {
nocopy( int );
nocopy( nocopy const & ) = delete;
nocopy( nocopy && ) = delete;
};
nocopy f() {
return { 3 };
}
это не:
nocopy f() {
return nocopy{ 3 };
}
Copy-list-initialization просто означает эквивалент синтаксисаnocopy X = { 3 }
используется для инициализации объекта, представляющего возвращаемое значение. Это не вызывает копию, и это оказывается идентичным примеру 8.5.4 / 6 продления времени жизни массива.
И Clang и GCC делаютсогласны по этому вопросу.
Другие заметкиОбзорN2640 не упоминает об этом угловом случае. Здесь широко обсуждались отдельные особенности, но я ничего не вижу об их взаимодействии.
Реализация этого становится проблематичной, поскольку сводится к возврату необязательного массива переменной длины по значению. Посколькуstd::initializer_list
не владеет своим содержимым, функция должна также возвращать что-то еще, что делает. При передаче в функцию это просто локальный массив фиксированного размера. Но в другом направлении VLA необходимо вернуть в стек вместе сstd::initializer_list
Указатели. Затем необходимо сообщить вызывающей стороне, следует ли удалять последовательность (находятся ли они в стеке или нет).
На эту проблему очень легко наткнуться, возвращая список фигурных инициализаций из лямбда-функции как «естественный» способ вернуть несколько временных объектов, не заботясь о том, как они содержатся.
auto && il = []() -> std::initializer_list< noisydt >
{ return { noisydt{}, noisydt{} }; }();
Действительно, это похоже на то, как я сюда попал. Но было бы ошибкой опускать->
trailing-return-type, потому что вывод лямбда-типа возврата происходит только тогда, когда возвращается выражение, а список фигурных скобок не является выражением.