Kapan menggunakan penginisialisasi tertutup kurung kurawal?

94

Di C ++ 11, kita memiliki sintaks baru untuk menginisialisasi kelas yang memberi kita banyak kemungkinan bagaimana menginisialisasi variabel.

{ // Example 1
  int b(1);
  int a{1};
  int c = 1;
  int d = {1};
}
{ // Example 2
  std::complex<double> b(3,4);
  std::complex<double> a{3,4};
  std::complex<double> c = {3,4};
  auto d = std::complex<double>(3,4);
  auto e = std::complex<double>{3,4};
}
{ // Example 3
  std::string a(3,'x');
  std::string b{3,'x'}; // oops
}
{ // Example 4
  std::function<int(int,int)> a(std::plus<int>());
  std::function<int(int,int)> b{std::plus<int>()};
}
{ // Example 5
  std::unique_ptr<int> a(new int(5));
  std::unique_ptr<int> b{new int(5)};
}
{ // Example 6
  std::locale::global(std::locale("")); // copied from 22.4.8.3
  std::locale::global(std::locale{""});
}
{ // Example 7
  std::default_random_engine a {}; // Stroustrup's FAQ
  std::default_random_engine b;
}
{ // Example 8
  duration<long> a = 5; // Stroustrup's FAQ too
  duration<long> b(5);
  duration<long> c {5};
}

Untuk setiap variabel yang saya nyatakan, saya harus memikirkan sintaks inisialisasi mana yang harus saya gunakan dan ini memperlambat kecepatan pengkodean saya. Saya yakin itu bukan maksud memperkenalkan tanda kurung kurawal.

Dalam hal kode template, mengubah sintaks dapat menghasilkan arti yang berbeda, jadi pergi ke cara yang benar sangatlah penting.

Saya bertanya-tanya apakah ada pedoman universal sintaks mana yang harus dipilih.

helami
sumber
1
Contoh perilaku yang tidak diinginkan dari {} inisialisasi: string (50, 'x') vs string {50, 'x'} di sini
P i

Jawaban:

64

Saya pikir yang berikut ini bisa menjadi pedoman yang baik:

  • Jika nilai (tunggal) yang Anda inisialisasi dimaksudkan untuk menjadi nilai yang tepat dari objek, gunakan =inisialisasi copy ( ) (karena jika terjadi kesalahan, Anda tidak akan pernah secara tidak sengaja memanggil konstruktor eksplisit, yang umumnya menafsirkan nilai yang diberikan berbeda). Di tempat di mana inisialisasi salinan tidak tersedia, lihat apakah inisialisasi brace memiliki semantik yang benar, dan jika demikian, gunakan itu; jika tidak, gunakan inisialisasi tanda kurung (jika itu juga tidak tersedia, Anda beruntung).

  • Jika nilai yang Anda inisialisasi adalah daftar nilai yang akan disimpan dalam objek (seperti elemen vektor / larik, atau bagian nyata / imajiner dari bilangan kompleks), gunakan inisialisasi kurung kurawal jika tersedia.

  • Jika nilai yang Anda inisialisasi bukanlah nilai yang akan disimpan, tetapi mendeskripsikan nilai / status objek yang diinginkan, gunakan tanda kurung. Contohnya adalah argumen ukuran dari sebuah vectoratau argumen nama file dari sebuah fstream.

celtschk
sumber
4
@ user1304032: Lokal bukanlah sebuah string, oleh karena itu Anda tidak akan menggunakan inisialisasi salinan. Lokal juga tidak berisi string (mungkin menyimpan string itu sebagai detail implementasi, tetapi itu bukan tujuannya), oleh karena itu Anda tidak akan menggunakan inisialisasi brace. Oleh karena itu pedoman mengatakan untuk menggunakan inisialisasi tanda kurung.
celtschk
2
Saya pribadi paling menyukai pedoman ini dan juga berfungsi dengan baik pada kode generik. Ada beberapa pengecualian ( T {}atau alasan sintaksis seperti penguraian yang paling menjengkelkan ), tetapi secara umum menurut saya ini merupakan saran yang baik. Perhatikan bahwa ini adalah pendapat subjektif saya, jadi orang harus melihat jawaban lain juga.
helami
2
@celtschk: Itu tidak akan berfungsi untuk jenis yang tidak dapat disalin, tidak dapat dipindahkan; type var{};tidak.
ildjarn
2
@celtschk: Saya tidak mengatakan itu adalah sesuatu yang akan sering terjadi, tetapi ini lebih sedikit mengetik dan bekerja dalam lebih banyak konteks, jadi apa sisi negatifnya?
ildjarn
2
Pedoman saya jelas tidak pernah meminta inisialisasi salinan. ; -]
ildjarn
26

Saya cukup yakin tidak akan pernah ada pedoman universal. Pendekatan saya adalah menggunakan kurung kurawal untuk mengingat hal itu

  1. Konstruktor daftar penginisialisasi lebih diutamakan daripada konstruktor lainnya
  2. Semua wadah pustaka standar dan std :: basic_string memiliki konstruktor daftar penginisialisasi.
  3. Inisialisasi kurung kurawal tidak memungkinkan konversi mempersempit.

Jadi kawat gigi bulat dan keriting tidak bisa saling menggantikan. Tetapi mengetahui di mana perbedaannya memungkinkan saya untuk menggunakan inisialisasi kurung kurawal dalam banyak kasus (beberapa kasus di mana saya tidak bisa saat ini adalah bug kompiler).

juanchopanza
sumber
6
Keriting kurawal memiliki kelemahan yang bisa saya sebut konstruktor daftar secara tidak sengaja. Tanda kurung bulat tidak. Bukankah itu alasan untuk menggunakan tanda kurung bulat secara default?
helami
4
@pengguna: int i = 0;Saya rasa tidak ada orang yang akan menggunakannya di int i{0}sana, dan mungkin membingungkan (juga, 0jika tipe int, jadi tidak akan ada penyempitan ). Untuk yang lainnya, saya akan mengikuti saran Juancho: prefer {}, waspadalah terhadap beberapa kasus yang tidak semestinya Anda lakukan. Perhatikan bahwa tidak banyak tipe yang akan mengambil daftar penginisialisasi sebagai argumen konstruktor, Anda dapat mengharapkan kontainer dan tipe seperti kontainer (tuple ...) memilikinya, tetapi kebanyakan kode akan memanggil konstruktor yang sesuai.
David Rodríguez - dribeas
3
@ user1304032 tergantung apakah Anda peduli dengan penyempitan. Ya, jadi saya lebih suka kompiler memberi tahu saya bahwa itu int i{some floating point}adalah kesalahan, daripada memotongnya secara diam-diam.
juanchopanza
3
Mengenai "prefer {}, waspadalah terhadap beberapa kasus di mana Anda tidak boleh": Katakanlah dua kelas memiliki konstruktor yang secara semantik setara tetapi satu kelas memiliki daftar penginisialisasi juga. Haruskah dua konstruktor ekivalen disebut berbeda?
helami
3
@helami: "Katakanlah dua kelas memiliki konstruktor yang secara semantik setara tetapi satu kelas juga memiliki daftar penginisialisasi. Haruskah dua konstruktor yang setara dipanggil secara berbeda?" Katakanlah saya mengalami parse yang paling menjengkelkan; yang dapat terjadi pada setiap konstruktor untuk setiap contoh. Jauh lebih mudah untuk menghindari ini jika Anda hanya menggunakan {}maksud "menginisialisasi" kecuali Anda benar - benar tidak bisa .
Nicol Bolas
16

Di luar kode generik (yaitu template), Anda dapat (dan saya melakukannya) menggunakan tanda kurung di mana saja . Salah satu keuntungannya adalah ia berfungsi di mana-mana, misalnya bahkan untuk inisialisasi di dalam kelas:

struct foo {
    // Ok
    std::string a = { "foo" };

    // Also ok
    std::string b { "bar" };

    // Not possible
    std::string c("qux");

    // For completeness this is possible
    std::string d = "baz";
};

atau untuk argumen fungsi:

void foo(std::pair<int, double*>);
foo({ 42, nullptr });
// Not possible with parentheses without spelling out the type:
foo(std::pair<int, double*>(42, nullptr));

Untuk variabel saya tidak terlalu memperhatikan antara gaya T t = { init };atau T t { init };, saya menemukan perbedaannya kecil dan paling buruk hanya akan menghasilkan pesan kompilator yang membantu tentang penyalahgunaan explicitkonstruktor.

Untuk tipe yang menerima std::initializer_listmeskipun jelas terkadang non- std::initializer_listkonstruktor dibutuhkan (contoh klasiknya std::vector<int> twenty_answers(20, 42);). Tidak apa-apa jika tidak menggunakan kawat gigi.


Ketika sampai pada kode generik (yaitu dalam template), paragraf terakhir seharusnya telah memunculkan beberapa peringatan. Pertimbangkan hal berikut:

template<typename T, typename... Args>
std::unique_ptr<T> make_unique(Args&&... args)
{ return std::unique_ptr<T> { new T { std::forward<Args>(args)... } }; }

Kemudian auto p = make_unique<std::vector<T>>(20, T {});buat vektor ukuran 2 if Tis eg int, atau vector size 20 if Tis std::string. Tanda yang sangat jelas bahwa ada sesuatu yang sangat salah terjadi di sini adalah bahwa tidak ada sifat yang dapat menyelamatkan Anda di sini (misalnya dengan SFINAE): std::is_constructibledalam hal inisialisasi langsung, sedangkan kami menggunakan inisialisasi brace yang menolak mengarahkan- inisialisasi jika dan hanya jika tidak ada konstruktor yang melakukan std::initializer_listinterferensi. Demikian pula std::is_convertibletidak membantu.

Saya telah menyelidiki apakah sebenarnya mungkin untuk mengubah sifat yang dapat memperbaikinya, tetapi saya tidak terlalu optimis tentang itu. Bagaimanapun, saya tidak berpikir kita akan kehilangan banyak, saya pikir fakta yang make_unique<T>(foo, bar)menghasilkan konstruksi yang setara dengan T(foo, bar)sangat intuitif; terutama mengingat yang make_unique<T>({ foo, bar })sangat berbeda dan hanya masuk akal jika foodan barmemiliki tipe yang sama.

Karenanya untuk kode generik saya hanya menggunakan tanda kurung untuk inisialisasi nilai (misalnya T t {};atau T t = {};), yang sangat nyaman dan menurut saya lebih unggul dari cara C ++ 03 T t = T();. Jika tidak, itu baik sintaks inisialisasi langsung (yaitu T t(a0, a1, a2);), atau kadang-kadang konstruksi default ( T t; stream >> t;menjadi satu-satunya kasus di mana saya menggunakan yang menurut saya).

Itu tidak berarti bahwa semua kawat gigi buruk, pertimbangkan contoh sebelumnya dengan perbaikan:

template<typename T, typename... Args>
std::unique_ptr<T> make_unique(Args&&... args)
{ return std::unique_ptr<T> { new T(std::forward<Args>(args)...) }; }

Ini masih menggunakan tanda kurung kurawal untuk membuat std::unique_ptr<T>, meskipun jenis sebenarnya bergantung pada parameter template T.

Luc Danton
sumber
@interjay Beberapa contoh saya mungkin memang perlu menggunakan tipe unsigned sebagai gantinya, misalnya make_unique<T>(20u, T {})untuk Tmenjadi salah satu unsignedatau std::string. Tidak terlalu yakin dengan detailnya. (Perhatikan bahwa saya juga mengomentari ekspektasi mengenai inisialisasi langsung vs inisialisasi brace mengenai misalnya fungsi penerusan sempurna.) std::string c("qux");Belum ditentukan untuk bekerja sebagai inisialisasi di dalam kelas untuk menghindari ambiguitas dengan deklarasi fungsi anggota dalam tata bahasa.
Luc Danton
@interjay Saya tidak setuju dengan Anda pada poin pertama, silakan periksa 8.5.4 Inisialisasi daftar dan 13.3.1.7 Inisialisasi dengan inisialisasi daftar. Adapun yang kedua, Anda perlu melihat lebih dekat apa yang saya tulis (yang berkenaan dengan inisialisasi di kelas ) dan / atau tata bahasa C ++ (misalnya anggota-deklarator , yang merujuk pada brace-or-equal-initializer ).
Luc Danton
Hmm, Anda benar - Saya telah menguji dengan GCC 4.5 sebelumnya yang sepertinya mengkonfirmasi apa yang saya katakan, tetapi GCC 4.6 setuju dengan Anda. Dan saya merindukan fakta yang Anda bicarakan tentang inisialisasi di dalam kelas. Permintaan maaf saya.
interjay