Список ініціалізаторів всередині 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 з вектором унікального_ptr, наприклад. Розмір initializer_listне є константою часу компіляції. А те, що std::vector<int>(3)і std::vector<int>{3}роблять зовсім інші речі. Мене сумно :(
bolov


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тоді призводить до невизначеної поведінки.

Демо демо тут .

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.