Список инициализаторов внутри std :: pair

26

Этот код:

#include <iostream>
#include <string>

std::pair<std::initializer_list<std::string>, int> groups{ { "A", "B" }, 0 };

int main()
{
    for (const auto& i : groups.first)
    {
        std::cout << i << '\n';
    }
    return 0;
}

компилируется, но возвращает segfault. Почему?

Протестировано на gcc 8.3.0 и на онлайн-компиляторах.

рин
источник
1
Для удобства: Godbolt ссылки с и без std::pair .
Макс Лангхоф

Ответы:

24

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

Для хранения вы должны использовать контейнер, как std::vectorили std::array.

Болов
источник
Меня беспокоит, что это компилируемо. Глупый язык :(
Гонки
1
@LightnessRaceswithMonica У меня много говядины initializer_list. Невозможно использовать объекты только для перемещения, поэтому вы не можете использовать список init с вектором unique_ptr, например. Размер initializer_listне является константой времени компиляции. А то, что std::vector<int>(3)и std::vector<int>{3}делаю совершенно разные вещи. Мне грустно :(
болов
Да, то же самое ... :(
Гонки
3

Я бы просто добавил немного больше деталей. Базовый массив std::initializer_listведет себя примерно так же, как временные. Рассмотрим следующий класс:

struct X
{
   X(int i) { std::cerr << "ctor\n"; }
   ~X() { std::cerr << "dtor\n"; }
};

и его использование в следующем коде:

std::pair<const X&, int> p(1, 2);
std::cerr << "barrier\n";

Распечатывает

ctor
dtor
barrier

поскольку в первой строке создается временный экземпляр типа X(путем преобразования конструктора из 1) и также уничтожается. Ссылка, хранящаяся в pзатем болтается.

Что касается std::initializer_list, если вы используете это так:

{
   std::initializer_list<X> l { 1, 2 };
   std::cerr << "barrier\n";
}

затем базовый (временный) массив существует до тех пор, пока не lзавершится. Поэтому на выходе получается:

ctor
ctor
barrier
dtor
dtor

Однако, если вы переключитесь на

std::pair<std::initializer_list<X>, int> l { {1}, 2 };
std::cerr << "barrier\n";

Выход снова

ctor
dtor
barrier

поскольку базовый (временный) массив существует только в первой строке. Разыменование указателя на элементы lзатем приводит к неопределенному поведению.

Живая демоверсия здесь .

Даниэль Лангр
источник