Gaya pengkodean pada akhirnya subyektif, dan sangat tidak mungkin manfaat kinerja yang besar akan datang darinya. Tapi inilah yang akan saya katakan bahwa Anda dapatkan dari penggunaan liberal inisialisasi seragam:
Minimalkan Redundant Typenames
Pertimbangkan yang berikut ini:
vec3 GetValue()
{
return vec3(x, y, z);
}
Mengapa saya harus mengetik vec3
dua kali? Apakah ada gunanya? Kompiler tahu dengan baik dan baik apa fungsi kembali. Mengapa saya tidak bisa mengatakan, "panggil konstruktor dari apa yang saya kembalikan dengan nilai-nilai ini dan kembalikan?" Dengan inisialisasi yang seragam, saya dapat:
vec3 GetValue()
{
return {x, y, z};
}
Semuanya berfungsi.
Bahkan lebih baik untuk argumen fungsi. Pertimbangkan ini:
void DoSomething(const std::string &str);
DoSomething("A string.");
Itu bekerja tanpa harus mengetikkan nama samaran, karena std::string
tahu bagaimana membangun sendiri dari yang const char*
tersirat. Itu hebat. Tetapi bagaimana jika string itu berasal, katakan RapidXML. Atau string Lua. Artinya, katakanlah saya benar-benar tahu panjang tali di depan. The std::string
konstruktor yang mengambil const char*
harus mengambil panjang string jika saya hanya lulus const char*
.
Ada kelebihan beban yang membutuhkan waktu lama secara eksplisit. Tapi untuk menggunakannya, aku harus melakukan ini: DoSomething(std::string(strValue, strLen))
. Mengapa ada nama ketik tambahan di sana? Kompiler tahu apa jenisnya. Sama seperti dengan auto
, kita dapat menghindari memiliki nama ketik tambahan:
DoSomething({strValue, strLen});
Itu hanya bekerja. Tanpa nama, tidak ada masalah, tidak ada. Kompiler melakukan tugasnya, kode lebih pendek, dan semua orang senang.
Memang, ada argumen yang dibuat bahwa versi pertama ( DoSomething(std::string(strValue, strLen))
) lebih terbaca. Artinya, sudah jelas apa yang terjadi dan siapa yang melakukan apa. Itu benar, sampai batas tertentu; memahami kode berbasis inisialisasi seragam memerlukan melihat prototipe fungsi. Ini adalah alasan yang sama mengapa beberapa orang mengatakan Anda tidak boleh melewati parameter dengan referensi non-const: sehingga Anda dapat melihat di situs panggilan jika suatu nilai sedang dimodifikasi.
Tetapi hal yang sama bisa dikatakan untuk auto
; mengetahui apa yang Anda dapatkan dari auto v = GetSomething();
memerlukan melihat definisi GetSomething
. Tetapi itu tidak berhenti auto
dari digunakan dengan pengabaian yang hampir sembrono begitu Anda memiliki akses ke sana. Secara pribadi, saya pikir itu akan baik-baik saja setelah Anda terbiasa. Apalagi dengan IDE yang bagus.
Never Get The Most Vexing Parse
Ini beberapa kode.
class Bar;
void Func()
{
int foo(Bar());
}
Kuis pop: apa itu foo
? Jika Anda menjawab "variabel", Anda salah. Ini sebenarnya prototipe dari fungsi yang mengambil sebagai parameternya fungsi yang mengembalikan Bar
, dan nilai pengembalian foo
fungsi adalah int.
Ini disebut C ++ "Most Vexing Parse" karena sama sekali tidak masuk akal bagi manusia. Tetapi aturan C ++ sayangnya memerlukan ini: jika mungkin dapat diartikan sebagai prototipe fungsi, maka itu akan menjadi. Masalahnya adalah Bar()
; itu bisa menjadi salah satu dari dua hal. Itu bisa berupa tipe yang dinamai Bar
, yang artinya membuat sementara. Atau bisa juga fungsi yang tidak mengambil parameter dan mengembalikan a Bar
.
Inisialisasi seragam tidak dapat diartikan sebagai prototipe fungsi:
class Bar;
void Func()
{
int foo{Bar{}};
}
Bar{}
selalu menciptakan sementara. int foo{...}
selalu membuat variabel.
Ada banyak kasus di mana Anda ingin menggunakan Typename()
tetapi tidak bisa karena aturan parsing C ++. Dengan Typename{}
, tidak ada ambiguitas.
Alasan untuk tidak
Satu-satunya kekuatan nyata yang Anda berikan adalah penyempitan. Anda tidak dapat menginisialisasi nilai yang lebih kecil dengan yang lebih besar dengan inisialisasi seragam.
int val{5.2};
Itu tidak akan dikompilasi. Anda dapat melakukannya dengan inisialisasi kuno, tetapi bukan inisialisasi seragam.
Ini dilakukan sebagian untuk membuat daftar penginisialisasi benar-benar berfungsi. Kalau tidak, akan ada banyak kasus ambigu berkaitan dengan jenis daftar penginisialisasi.
Tentu saja, beberapa orang mungkin berpendapat bahwa kode seperti itu pantas untuk tidak dikompilasi. Saya pribadi setuju; penyempitan sangat berbahaya dan dapat menyebabkan perilaku yang tidak menyenangkan. Mungkin lebih baik untuk menangkap masalah tersebut sejak awal pada tahap kompiler. Paling tidak, penyempitan menunjukkan bahwa seseorang tidak berpikir terlalu keras tentang kode.
Perhatikan bahwa penyusun umumnya akan memperingatkan Anda tentang hal semacam ini jika tingkat peringatan Anda tinggi. Jadi sungguh, semua ini dilakukan adalah membuat peringatan menjadi kesalahan yang ditegakkan. Beberapa orang mungkin mengatakan bahwa Anda seharusnya tetap melakukannya;)
Ada satu alasan lain untuk tidak:
std::vector<int> v{100};
Apa fungsinya? Itu bisa membuat vector<int>
dengan seratus item yang dibangun default. Atau bisa membuat vector<int>
dengan 1 item yang nilainya 100
. Secara teori keduanya mungkin.
Dalam kenyataannya, ia melakukan yang terakhir.
Mengapa? Daftar inisialisasi menggunakan sintaksis yang sama dengan inisialisasi seragam. Jadi harus ada beberapa aturan untuk menjelaskan apa yang harus dilakukan dalam kasus ambiguitas. Aturannya cukup sederhana: jika kompiler dapat menggunakan konstruktor daftar initializer dengan daftar inisialisasi brace, maka ia akan melakukannya . Karena vector<int>
memiliki konstruktor daftar penginisialisasi yang mengambil initializer_list<int>
, dan {100} dapat menjadi valid initializer_list<int>
, maka itu harus .
Untuk mendapatkan konstruktor ukuran, Anda harus menggunakan ()
sebagai gantinya {}
.
Perhatikan bahwa jika ini adalah vector
sesuatu yang tidak dapat dikonversi ke integer, ini tidak akan terjadi. Initializer_list tidak cocok dengan konstruktor daftar initializer dari vector
tipe itu, dan karenanya kompiler bebas untuk memilih dari konstruktor lain.