Lista inicjująca wewnątrz std :: pair

26

Ten kod:

#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;
}

kompiluje, ale zwraca segfault. Dlaczego?

Testowany na gcc 8.3.0 i na kompilatorach online.

płukanie
źródło
1
Dla wygody: Godbolt linki z i bez std::pair .
Max Langhof,

Odpowiedzi:

24

std::initializer_listnie jest przeznaczony do przechowywania, jest po prostu przeznaczony ... do inicjalizacji. Wewnętrznie przechowuje wskaźnik do pierwszego elementu i rozmiaru. W twoim kodzie std::stringobiekty są tymczasowe i initializer_listżadne z nich nie bierze na siebie własności, ani nie przedłuża ich życia, ani nie kopiuje (ponieważ nie jest to kontener), więc wychodzą poza zakres natychmiast po utworzeniu, ale initializer_listnadal masz do nich wskaźnik. Dlatego pojawia się błąd segmentacji.

Do przechowywania należy użyć pojemnika, takiego jak std::vectorlub std::array.

bolov
źródło
Niepokoi mnie, że można to skompilować. Głupi język :(
Lekkość ściga się na orbicie
1
@LightnessRaceswithMonica Mam dużo wołowiny initializer_list. Nie można używać obiektów tylko do ruchu, więc nie można na przykład użyć init list z wektorem Unique_ptr. Rozmiar initializer_listnie jest stałą czasową kompilacji. I fakt, że std::vector<int>(3)i std::vector<int>{3}robić zupełnie inne rzeczy.
Zasmuca
Tak samo ... :(
Lekkość ściga się na orbicie
3

Chciałbym tylko dodać trochę więcej szczegółów. Podstawowa tablica std::initializer_listzachowuje się podobnie jak tymczasowe. Rozważ następującą klasę:

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

i jego użycie w następującym kodzie:

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

Drukuje

ctor
dtor
barrier

ponieważ w pierwszym wierszu Xtworzona jest tymczasowa instancja typu (poprzez konwersję konstruktora z 1), a także niszczona. Zapisane w nim odwołanie pzwisa.

Jeśli zaś std::initializer_listużywasz go w ten sposób:

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

wtedy podstawowa (tymczasowa) tablica istnieje tak długo, jak lwyjścia. Dlatego wynik jest następujący:

ctor
ctor
barrier
dtor
dtor

Jeśli jednak przełączysz się na

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

Wyjście jest znowu

ctor
dtor
barrier

ponieważ podstawowa (tymczasowa) tablica istnieje tylko w pierwszym wierszu. Odsunięcie wskaźnika do elementów, la następnie powoduje niezdefiniowane zachowanie.

Demo na żywo jest tutaj .

Daniel Langr
źródło