Initialisierungsliste in std :: pair

26

Dieser Code:

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

Kompiliert, gibt aber segfault zurück. Warum?

Getestet auf gcc 8.3.0 und auf Online-Compilern.

rin
quelle
1
Der Einfachheit halber: Godbolt verbindet mit und ohne std::pair .
Max Langhof

Antworten:

24

std::initializer_listist nicht zum Speichern gedacht, sondern nur zum ... gut Initialisieren. Intern wird nur ein Zeiger auf das erste Element und die Größe gespeichert. In Ihrem Code sind die std::stringObjekte temporär und initializer_listkeiner übernimmt das Eigentum an ihnen, verlängert weder ihre Lebensdauer noch kopiert er sie (weil es sich nicht um einen Container handelt), sodass sie unmittelbar nach der Erstellung den Gültigkeitsbereich verlassen, aber Sie haben initializer_listimmer noch einen Zeiger auf sie. Deshalb erhalten Sie einen Segmentierungsfehler.

Zum Aufbewahren sollten Sie einen Behälter wie std::vectoroder verwenden std::array.

Bolov
quelle
Es stört mich, dass dies kompilierbar ist. Dumme Sprache :(
Leichtigkeitsrennen im Orbit
1
@ LightnessRaceswithMonica Ich habe viel Rindfleisch mit initializer_list. Es ist nicht möglich, nur Verschiebungsobjekte zu verwenden, daher können Sie beispielsweise list init mit dem Vektor unique_ptr nicht verwenden. Die Größe von initializer_listist keine Kompilierungszeitkonstante. Und die Tatsache, dass std::vector<int>(3)und std::vector<int>{3}ganz andere Dinge tun. Macht mich traurig :(
Bolov
Ja das gleiche ... :(
Leichtigkeitsrennen im Orbit
3

Ich würde nur ein bisschen mehr Details hinzufügen. Ein zugrunde liegendes Array von std::initializer_listVerhaltensweisen verhält sich ähnlich wie temporäre. Betrachten Sie die folgende Klasse:

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

und seine Verwendung im folgenden Code:

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

Es wird ausgedruckt

ctor
dtor
barrier

da in der ersten Zeile eine temporäre Instanz des Typs Xerstellt (durch Konvertieren des Konstruktors von 1) und ebenfalls zerstört wird. Die darin gespeicherte Referenz pbaumelt dann.

Was std::initializer_list, wenn Sie es auf diese Weise verwendet werden :

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

Dann existiert das zugrunde liegende (temporäre) Array, solange es beendet wird l. Daher lautet die Ausgabe:

ctor
ctor
barrier
dtor
dtor

Wenn Sie jedoch zu wechseln

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

Die Ausgabe ist wieder

ctor
dtor
barrier

da das zugrunde liegende (temporäre) Array nur in der ersten Zeile vorhanden ist. Das Dereferenzieren des Zeigers auf die Elemente von lführt zu einem undefinierten Verhalten.

Live-Demo ist da .

Daniel Langr
quelle