G CC выдает предупреждение init-list-Life о потенциально допустимом коде? - PullRequest
4 голосов
/ 13 июля 2020

Я работаю на Debian unstable с G CC 9.3.0.

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

#include <initializer_list>
#include <map>
#include <vector>

std::map<int, std::vector<int>> ex = []{
    /* for reused lists */
    std::initializer_list<int> module_options;

    return (decltype(ex)) {
        {1, module_options = {
            1, 2, 3
        }},
        {2, module_options},
    };
}();

Идея состоит в том, что идентичные подразделы списков инициализаторов сначала объявляются вверху, определяются и присваиваются переменной std:initializer_list при первом использовании, а затем используются в нескольких местах. Это удобно, и некоторые могут поспорить, что это более читабельно, поэтому оно было принято.

Все было хорошо до тех пор, пока не было go, когда G CC начал выдавать предупреждение init-list-lifetime на код . Мы используем -Werror в нашей регрессии, так что для меня это не регрессия. Я также попытался скомпилировать с помощью clang 9.0.1, который не выдает предупреждения.

<source>: In lambda function:
<source>:12:9: warning: assignment from temporary 'initializer_list' does not extend the lifetime of the underlying array [-Winit-list-lifetime]
   12 |         }},
      |         ^

Согласно cppreference :

Базовый массив не гарантированно существует после того, как истечет время жизни исходного объекта списка инициализаторов. Хранилище для std :: initializer_list не указано (т.е. это может быть автоматическая c, временная или постоянная c постоянная память, в зависимости от ситуации).

Итак, я понимаю что значение общего списка инициализаторов, определяемое в рамках охватывающего списка инициализаторов, имеет время жизни, которое заканчивается охватывающим списком инициализаторов. На странице cppreference ранее упоминалось, что std::initializer_list - это «облегченный прокси-объект», что означает, что он не становится владельцем временного объекта и не продлевает его время жизни. Это означает, что не гарантируется, что базовый массив будет существовать при дальнейшем использовании, поэтому возникает предупреждение. Правильный ли этот анализ?

Я могу предотвратить появление предупреждения, переместив инициализацию переменной std::initializer_list в объявление. Полную информацию о проблеме в том виде, в каком она стоит в проекте, см. В PR .

1 Ответ

1 голос
/ 14 июля 2020

Итак, я понимаю, что значение общего списка инициализаторов, определяемое в рамках охватывающего списка инициализаторов, имеет время жизни, которое заканчивается охватывающим списком инициализаторов

Вы говоря об объекте, созданном выражением prvalue {1, 2, 3}, верно?

Пример в decl.init.list / 6 ,

Массив имеет такое же время жизни, как и любой другой временный объект ([ class.porary ]), за исключением того, что инициализация объекта initializer_­list из массива продлевает время жизни массива точно так же, как привязка ссылки к временному объекту. [ Пример:

// ...
std::initializer_list<int> i3 = { 1, 2, 3 };
// ...

из которых в стандарте (или черновике) указано

Для i3 объект initializer_­list переменная, поэтому массив сохраняется в течение всего времени существования переменной.

Это говорит о том, что объект должен быть материализован и его время жизни должно быть увеличено.

Однако вы не инициализация объекта initializer_list из выражения, потому что ваша переменная уже инициализирована. Если бы мы переписали ваш код как вызов условного

module_options.operator=({1, 2, 3})

, то мы бы не ожидали, что временное время жизни будет продлено после окончания вызова функции.

Я подозревал, что Временный по-прежнему будет жить до конца полного выражения, поскольку я думал, что привязка ссылки к должна была продлить его время жизни, а не уменьшать его: но, по общему признанию, class.porary / 6 говорит "... сохраняется в течение всего времени существования ссылки ..." , а не "... сохраняется по крайней мере время жизни ..."

Однако это означает, что следующий вариант вашего исходного кода должен делать то, что вы хотите:

std::map<int, std::vector<int>> ex = []{
    /* for reused lists */
    std::initializer_list<int> module_options { 1, 2, 3 };

    return (decltype(ex)) {
        {1, module_options},
        {2, module_options},
    };
}();
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...