Daftar inisialisasi di dalam std :: pair

26

Kode ini:

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

mengkompilasi tetapi mengembalikan segfault. Mengapa?

Diuji pada gcc 8.3.0 dan pada kompiler online.

rin
sumber
1
Untuk kenyamanan: tautan Godbolt dengan dan tanpa std::pair .
Max Langhof

Jawaban:

24

std::initializer_listtidak dimaksudkan untuk disimpan, itu hanya dimaksudkan untuk ... inisialisasi baik. Secara internal itu hanya menyimpan pointer ke elemen pertama dan ukurannya. Dalam kode Anda, std::stringobjek adalah temporer dan initializer_listtidak ada yang memiliki kepemilikan atas mereka, tidak memperpanjang usia mereka, tidak juga menyalinnya (karena itu bukan wadah) sehingga mereka keluar dari ruang lingkup segera setelah penciptaan, tetapi Anda initializer_listmasih memegang pointer ke mereka. Itu sebabnya Anda mendapatkan kesalahan segmentasi.

Untuk menyimpan, Anda harus menggunakan wadah, seperti std::vectoratau std::array.

bolov
sumber
Itu mengganggu saya bahwa ini dapat dikompilasi. Bahasa konyol :(
Races Lightness di Orbit
1
@LightnessRaceswithMonica Saya punya banyak daging sapi initializer_list. Tidak mungkin untuk menggunakan objek hanya bergerak, jadi Anda tidak dapat menggunakan init daftar dengan vektor unique_ptr misalnya. Ukuran initializer_listbukan konstanta waktu kompilasi. Dan fakta itu std::vector<int>(3)dan std::vector<int>{3}melakukan hal-hal yang sama sekali berbeda. Membuatku sedih :(
bolov
Ya sama ... :(
Lightness Races di Orbit
3

Saya hanya akan menambahkan sedikit rincian lebih lanjut. Array yang mendasari std::initializer_listberperilaku jenis-sama seperti temporaries. Pertimbangkan kelas berikut:

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

dan penggunaannya dalam kode berikut:

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

Mencetak

ctor
dtor
barrier

sejak pada baris pertama, instance sementara dari tipe Xdibuat (dengan mengubah konstruktor dari 1) dan dihancurkan juga. Referensi yang disimpan ke dalam pkemudian menggantung.

Adapun std::initializer_list, jika Anda menggunakannya dengan cara ini:

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

kemudian, array (sementara) yang mendasari ada selama lkeluar. Oleh karena itu, outputnya adalah:

ctor
ctor
barrier
dtor
dtor

Namun, jika Anda beralih ke

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

Outputnya lagi

ctor
dtor
barrier

karena array (sementara) yang mendasarinya hanya ada di baris pertama. Mendereferensikan pointer ke elemen yang lkemudian menghasilkan perilaku yang tidak terdefinisi.

Demo langsung ada di sini .

Daniel Langr
sumber