
Telah diketahui bahwa semantik inisialisasi adalah salah satu bagian paling kompleks dari C ++. Ada banyak jenis inisialisasi, dijelaskan oleh sintaks yang berbeda, dan mereka semua berinteraksi dengan cara yang kompleks dan menantang. C ++ 11 mengusung konsep "inisialisasi universal". Sayangnya, dia memperkenalkan aturan yang lebih kompleks, dan pada gilirannya, mereka diblokir di C ++ 14, C ++ 17 dan diubah lagi di C ++ 20.
Di bawah cut - video dan terjemahan laporan Timur Doumler dari konferensi C ++ Rusia . Timur pertama-tama merangkum hasil historis dari evolusi inisialisasi dalam C ++, memberikan tinjauan sistematis tentang versi saat ini dari aturan inisialisasi, masalah khas dan kejutan, menjelaskan bagaimana menggunakan semua aturan ini secara efektif, dan akhirnya berbicara tentang proposal baru dalam standar yang dapat membuat inisialisasi semantik. C ++ 20 sedikit lebih nyaman. Selanjutnya ceritanya adalah atas namanya.
Daftar isi

Gif yang Anda lihat sekarang menyampaikan pesan utama laporan dengan sangat baik. Saya menemukannya di Internet sekitar enam bulan yang lalu, dan mempostingnya di Twitter saya. Dalam komentarnya, seseorang mengatakan bahwa tiga jenis inisialisasi tidak ada. Sebuah diskusi dimulai, di mana saya diundang untuk melaporkan hal ini. Dan semuanya dimulai.
Tentang inisialisasi Nikolay Yossutis sudah diceritakan . Laporannya termasuk slide daftar 19 cara berbeda untuk menginisialisasi sebuah int:
int i1;
Menurut saya ini adalah situasi unik untuk bahasa pemrograman. Menginisialisasi variabel adalah salah satu tindakan paling sederhana, tetapi dalam C ++ itu sama sekali tidak mudah dilakukan. Tidak mungkin bahwa bahasa ini memiliki bidang lain di mana dalam beberapa tahun terakhir akan ada banyak laporan penyimpangan dari standar, koreksi dan perubahan. Aturan inisialisasi berubah dari standar ke standar, dan ada banyak posting di Internet tentang bagaimana menginisialisasi dalam C ++. Karena itu, membuat tinjauan sistematis tentang hal itu adalah tugas yang tidak sepele.
Saya akan menyajikan materi dalam urutan kronologis: pertama kita akan berbicara tentang apa yang diwarisi dari C, kemudian tentang C ++ 98, kemudian tentang C ++ 03, C ++ 11, C ++ 14 dan C ++ 17. Kami akan membahas kesalahan umum, dan saya akan memberikan rekomendasi saya mengenai inisialisasi yang tepat. Saya juga akan berbicara tentang inovasi di C ++ 20. Tabel ikhtisar akan disajikan di bagian paling akhir laporan.
Inisialisasi Default (C)
Dalam C ++, banyak hal yang diwarisi dari C, itu sebabnya kita akan memulainya. Ada beberapa cara untuk menginisialisasi variabel dalam C. Mereka mungkin tidak diinisialisasi sama sekali, dan ini disebut inisialisasi default . Menurut pendapat saya, ini adalah nama yang tidak menguntungkan. Faktanya adalah bahwa tidak ada variabel yang diberi nilai default, itu hanya tidak diinisialisasi. Jika Anda beralih ke variabel yang tidak diinisialisasi dalam C ++ dan C, Anda mendapatkan perilaku yang tidak terdefinisi:
int main() { int i; return i;
Hal yang sama berlaku untuk tipe khusus: jika dalam beberapa struct
ada bidang yang tidak diinisialisasi, maka ketika mengaksesnya, perilaku tidak terdefinisi juga terjadi:
struct Widget { int i; int j; }; int main() { Widget widget; return widget.i;
Banyak konstruksi baru telah ditambahkan ke C ++: kelas, konstruktor, publik, privat, metode, tetapi tidak ada yang mempengaruhi perilaku yang baru saja dijelaskan. Jika beberapa elemen tidak diinisialisasi di kelas, maka ketika mengaksesnya, perilaku tidak terdefinisi terjadi:
class Widget { public: Widget() {} int get_i() const noexcept { return i; } int get_j() const noexcept { return j; } private: int i; int j; }; int main() { Widget widget; return widget.get_i();
Tidak ada cara ajaib untuk menginisialisasi elemen kelas di C ++ secara default. Ini adalah poin yang menarik, dan selama beberapa tahun pertama karir saya dengan C ++, saya tidak tahu ini. Baik kompiler maupun IDE, yang saya gunakan saat itu, mengingatkan saya akan hal ini. Kolega saya tidak memperhatikan fitur ini ketika memeriksa kode. Saya cukup yakin bahwa karena dia, ada beberapa bug yang cukup aneh dalam kode saya yang ditulis selama tahun-tahun ini. Tampak jelas bagi saya bahwa kelas harus menginisialisasi variabel mereka.
Dalam C ++ 98, Anda dapat menginisialisasi variabel menggunakan daftar penginisialisasi anggota. Tetapi solusi untuk masalah seperti itu tidak optimal, karena harus dilakukan di setiap konstruktor, dan ini mudah dilupakan. Selain itu, inisialisasi hasil dalam urutan di mana variabel dinyatakan, dan tidak dalam urutan daftar penginisialisasi anggota:
Dalam C ++ 11, inisialisasi anggota langsung ditambahkan, yang jauh lebih nyaman untuk digunakan. Mereka memungkinkan Anda untuk menginisialisasi semua variabel secara bersamaan, dan ini memberi keyakinan bahwa semua elemen diinisialisasi:
Rekomendasi pertama saya: kapan pun Anda bisa, selalu gunakan DMI (inisialisasi anggota langsung). Mereka dapat digunakan baik dengan tipe float
( float
dan int
), dan dengan objek. Kebiasaan menginisialisasi elemen membuat kita mendekati masalah ini secara lebih sadar.
Salin Inisialisasi (C)
Jadi, metode inisialisasi pertama yang diwarisi dari C adalah inisialisasi secara default, dan itu tidak boleh digunakan. Cara kedua adalah inisialisasi salin . Dalam hal ini, kami menunjukkan variabel dan melalui tanda sama dengan - nilainya:
Salinan inisialisasi juga digunakan ketika argumen dilewatkan ke fungsi dengan nilai, atau ketika objek dikembalikan dari fungsi dengan nilai:
Tanda yang sama mungkin memberi kesan bahwa suatu nilai sedang ditetapkan, tetapi ini tidak demikian. Inisialisasi salin bukan tugas nilai. Tidak akan ada apa pun tentang apropriasi dalam laporan ini.
Properti penting lain dari inisialisasi salinan: jika jenis nilai tidak cocok, urutan konversi dijalankan. Urutan konversi memiliki aturan tertentu, misalnya, ia tidak memanggil konstruktor eksplisit, karena mereka tidak mentransformasikan konstruktor. Oleh karena itu, jika Anda melakukan inisialisasi salin untuk objek yang konstruktornya ditandai sebagai eksplisit, kesalahan kompilasi terjadi:
struct Widget { explicit Widget(int) {} }; Widget w1 = 1;
Selain itu, jika ada konstruktor lain yang tidak eksplisit, tetapi jenisnya lebih buruk, maka salin inisialisasi akan menyebutnya, mengabaikan konstruktor eksplisit:
struct Widget { explicit Widget(int) {} Widget(double) {} }; Widget w1 = 1;
Inisialisasi Agregat (C)
Jenis inisialisasi ketiga yang ingin saya bicarakan adalah inisialisasi agregat . Itu dieksekusi ketika array diinisialisasi dengan serangkaian nilai dalam kurung:
int i[4] = {0, 1, 2, 3};
Jika Anda tidak menentukan ukuran array, maka itu diturunkan dari jumlah nilai yang terlampir dalam tanda kurung:
int j[] = {0, 1, 2, 3};
Inisialisasi yang sama digunakan untuk kelas agregat, yaitu, kelas yang hanya kumpulan elemen publik (ada beberapa aturan lagi dalam definisi kelas agregat, tetapi sekarang kita tidak akan membahasnya):
struct Widget { int i; float j; }; Widget widget = {1, 3.14159};
Sintaks ini bekerja bahkan dalam C dan C ++ 98, dan, dimulai dengan C ++ 11, Anda dapat melewati tanda sama dengan itu:
Widget widget{1, 3.14159};
Inisialisasi agregat sebenarnya menggunakan inisialisasi salin untuk setiap elemen. Oleh karena itu, jika Anda mencoba menggunakan inisialisasi agregat (baik dengan tanda sama dan tanpa itu) untuk beberapa objek dengan konstruktor eksplisit, maka salin inisialisasi dilakukan untuk setiap objek dan kesalahan kompilasi terjadi:
struct Widget { explicit Widget(int) {} }; struct Thingy { Widget w1, w2; }; int main() { Thingy thingy = {3, 4};
Dan jika ada konstruktor lain untuk objek-objek ini, non-eksplisit, maka itu disebut, bahkan jika itu lebih cocok untuk mengetik:
struct Widget { explicit Widget(int) {} Widget(double) {} }; struct Thingy { Widget w1, w2; }; int main() { Thingy thingy = {3, 4};
Mari kita pertimbangkan satu lagi properti inisialisasi agregat. Pertanyaan: nilai apa yang dihasilkan program ini?
struct Widget { int i; int j; }; int main() { Widget widget = {1}; return widget.j; }
Teks tersembunyiBenar, nol. Jika Anda melewatkan beberapa elemen dalam array nilai selama inisialisasi agregat, maka variabel yang sesuai diatur ke nol. Ini adalah properti yang sangat berguna, karena berkat itu tidak akan pernah ada elemen yang tidak diinisialisasi. Ini bekerja dengan kelas agregat dan dengan array:
Properti penting lainnya dari inisialisasi agregat adalah penghilangan tanda kurung (brace elision). Menurut Anda, nilai apa yang dihasilkan program ini? Ini memiliki Widget
, yang merupakan agregat dari dua nilai int
, dan Thingy
, agregat dari Widget
dan int
. Apa yang kita dapatkan jika kita memberikan dua nilai inisialisasi untuk itu: {1, 2}
?
struct Widget { int i; int j; }; struct Thingy { Widget w; int k; }; int main() { Thingy t = {1, 2}; return tk;
Teks tersembunyiJawabannya nol. Di sini kita berhadapan dengan subagregat, yaitu, dengan kelas agregat bersarang. Kelas semacam itu dapat diinisialisasi dengan menggunakan tanda kurung bersarang, tetapi Anda dapat melewati salah satu dari pasangan tanda kurung ini. Dalam hal ini, suatu traversal rekursif dari sub-agregat dilakukan, dan {1, 2}
ternyata setara dengan {{1, 2}, 0}
. Diakui, properti ini tidak sepenuhnya jelas.
Inisialisasi Statis (C)
Akhirnya, inisialisasi statis juga diwarisi dari C: variabel statis selalu diinisialisasi. Ini dapat dilakukan dengan beberapa cara. Variabel statis dapat diinisialisasi dengan ekspresi konstan. Dalam hal ini, inisialisasi terjadi pada waktu kompilasi. Jika Anda tidak menetapkan nilai apa pun ke variabel, maka nilai itu diinisialisasi ke nol:
static int i = 3;
Program ini mengembalikan 3 meskipun j
tidak diinisialisasi. Jika variabel diinisialisasi bukan oleh konstanta, tetapi oleh objek, masalah dapat muncul.
Berikut adalah contoh dari perpustakaan nyata yang sedang saya kerjakan:
static Colour red = {255, 0, 0};
Ada kelas Warna di dalamnya, dan warna primer (merah, hijau, biru) didefinisikan sebagai objek statis. Ini adalah tindakan yang valid, tetapi segera setelah objek statis lain muncul di inisialisasi yang red
digunakan, ketidakpastian muncul karena tidak ada urutan yang kaku di mana variabel diinisialisasi. Aplikasi Anda dapat mengakses variabel yang tidak diinisialisasi, dan kemudian macet. Untungnya, dalam C ++ 11 menjadi mungkin untuk menggunakan konstruktor constexpr
, dan kemudian kita berurusan dengan inisialisasi konstan. Dalam hal ini, tidak ada masalah dengan urutan inisialisasi.
Jadi, empat jenis inisialisasi diwarisi dari bahasa C: inisialisasi default, menyalin, agregat dan inisialisasi statis.
Inisialisasi Langsung (C ++ 98)
Mari kita beralih ke C ++ 98. Mungkin fitur terpenting yang membedakan C ++ dari C adalah konstruktornya. Berikut adalah contoh panggilan konstruktor:
Widget widget(1, 2); int(3);
Dengan menggunakan sintaks yang sama, Anda dapat menginisialisasi tipe int
seperti int
dan float
. Sintaks ini disebut inisialisasi langsung . Itu selalu dieksekusi ketika kita memiliki argumen dalam tanda kurung.
Untuk tipe int
( int
, bool
, float
) tidak ada perbedaan dari inisialisasi salinan di sini. Jika kita berbicara tentang tipe pengguna, maka, tidak seperti inisialisasi salin, dengan inisialisasi langsung, Anda dapat melewati beberapa argumen. Sebenarnya, untuk kepentingan ini, inisialisasi langsung ditemukan.
Selain itu, inisialisasi langsung tidak menjalankan urutan konversi. Alih-alih, konstruktor dipanggil menggunakan resolusi kelebihan beban. Inisialisasi langsung memiliki sintaks yang sama dengan panggilan fungsi, dan menggunakan logika yang sama dengan fungsi C ++ lainnya.
Oleh karena itu, dalam situasi dengan konstruktor eksplisit, inisialisasi langsung berfungsi dengan baik, meskipun inisialisasi salin menimbulkan kesalahan:
struct Widget { explicit Widget(int) {} }; Widget w1 = 1;
Dalam situasi dengan dua konstruktor, satu di antaranya eksplisit, dan yang kedua kurang sesuai jenisnya, yang pertama disebut dengan inisialisasi langsung, dan yang kedua disebut dengan salinan. Dalam situasi ini, mengubah sintaks akan menyebabkan panggilan ke konstruktor lain - ini sering dilupakan:
struct Widget { explicit Widget(int) {} Widget(double) {} }; Widget w1 = 1;
Inisialisasi langsung selalu digunakan ketika tanda kurung digunakan, termasuk ketika notasi permintaan konstruktor digunakan untuk menginisialisasi objek sementara, serta dalam ekspresi new
dengan inisialisasi dalam tanda kurung dan dalam ekspresi cast
:
useWidget(Widget(1, 2));
Sintaks ini ada selama C ++ itu sendiri ada, dan memiliki cacat penting yang disebutkan Nikolai dalam pidato utamanya: parse yang paling menjengkelkan . Ini berarti bahwa semua yang dapat dibaca oleh kompiler sebagai deklarasi (deklarasi), ia membaca persis seperti deklarasi.
Pertimbangkan contoh di mana ada kelas Widget
dan kelas Thingy
, dan konstruktor Thingy
yang menerima Widget
:
struct Widget {}; struct Thingy { Thingy(Widget) {} }; int main () { Thingy thingy(Widget()); }
Sekilas, tampaknya setelah inisialisasi Thingy
, Widget
default dibuat diteruskan ke sana, tetapi kenyataannya, fungsi tersebut dinyatakan di sini. Kode ini mendeklarasikan fungsi yang menerima fungsi lain sebagai input, yang tidak menerima apa pun sebagai input dan mengembalikan Widget
, dan fungsi pertama mengembalikan Thingy
. Kode dikompilasi tanpa kesalahan, tetapi tidak mungkin kami mencari perilaku seperti itu.
Inisialisasi Nilai (C ++ 03)
Mari kita beralih ke versi selanjutnya - C ++ 03. Secara umum diterima bahwa tidak ada perubahan signifikan dalam versi ini, tetapi tidak demikian. Di C ++ 03, inisialisasi nilai muncul, di mana tanda kurung kosong ditulis:
int main() { return int();
Di C ++ 98, perilaku tidak terdefinisi terjadi di sini karena inisialisasi dilakukan secara default, dan dimulai dengan C ++ 03 program ini mengembalikan nol.
Aturannya adalah ini: jika ada konstruktor default yang ditentukan pengguna, inisialisasi dengan nilai memanggil konstruktor ini, jika tidak nol dikembalikan.
Pertimbangkan situasi dengan konstruktor khusus secara lebih rinci:
struct Widget { int i; }; Widget get_widget() { return Widget();
Dalam program ini, fungsi menginisialisasi nilai untuk Widget
baru dan mengembalikannya. Kami memanggil fungsi ini dan mengakses elemen i
dari objek Widget
. Sejak C ++ 03, nilai kembali di sini adalah nol, karena tidak ada konstruktor default yang ditentukan pengguna. Dan jika konstruktor seperti itu ada, tetapi tidak menginisialisasi i
, maka kita mendapatkan perilaku yang tidak terdefinisi:
struct Widget { Widget() {}
Perlu dicatat bahwa "yang ditentukan pengguna" tidak berarti "yang ditentukan pengguna". Ini berarti bahwa pengguna harus menyediakan tubuh konstruktor, yaitu kurung kurawal. Jika dalam contoh di atas, ganti badan konstruktor dengan = default
(fitur ini ditambahkan dalam C ++ 11), arti dari perubahan program. Sekarang kami memiliki konstruktor yang ditentukan oleh pengguna (yang ditentukan pengguna), tetapi tidak disediakan oleh pengguna (yang disediakan pengguna), sehingga program mengembalikan nol:
struct Widget { Widget() = default;
Sekarang mari kita coba untuk Widget() = default
keluar dari kelas. Arti program telah berubah lagi: Widget() = default
dianggap sebagai konstruktor yang disediakan pengguna jika berada di luar kelas. Program mengembalikan perilaku tidak terdefinisi lagi.
struct Widget { Widget(); int i; }; Widget::Widget() = default;
Ada logika tertentu: konstruktor yang didefinisikan di luar kelas dapat berada di dalam unit terjemahan lain. Kompiler mungkin tidak melihat konstruktor ini, karena mungkin dalam file .cpp
lain. Oleh karena itu, kompilator tidak dapat menarik kesimpulan tentang konstruktor seperti itu, dan tidak dapat membedakan konstruktor dengan tubuh dari konstruktor dengan = default
.
Inisialisasi Universal (C ++ 11)
Ada banyak perubahan yang sangat penting dalam C ++ 11. Secara khusus, inisialisasi universal (seragam) diperkenalkan, yang saya lebih suka menyebutnya "inisialisasi unicorn" karena itu hanya ajaib. Mari kita lihat mengapa dia muncul.
Seperti yang sudah Anda perhatikan, dalam C ++ ada banyak sintaks inisialisasi yang berbeda dengan perilaku yang berbeda. Parsing menjengkelkan dengan tanda kurung menyebabkan banyak ketidaknyamanan. Para pengembang juga tidak suka bahwa inisialisasi agregat hanya dapat digunakan dengan array, tetapi tidak dengan wadah seperti std::vector
. Sebagai gantinya, Anda harus menjalankan .reserve
dan .push_back
, atau menggunakan semua jenis pustaka menyeramkan:
Pembuat bahasa mencoba memecahkan semua masalah ini dengan memperkenalkan sintaksis dengan kurung kurawal tetapi tanpa tanda yang sama. Diasumsikan bahwa ini akan menjadi sintaks tunggal untuk semua jenis, di mana kurung kurawal digunakan dan tidak ada masalah parse menjengkelkan. Dalam kebanyakan kasus, sintaks ini melakukan tugasnya.
Inisialisasi baru ini disebut inisialisasi daftar , dan datang dalam dua jenis: langsung dan salin. Dalam kasus pertama, hanya kurung kurawal yang digunakan, dalam kurung kurawal kedua dengan tanda sama:
Daftar yang digunakan untuk inisialisasi disebut braced-init-list . Penting bahwa daftar ini bukan objek, tidak memiliki tipe. Beralih ke C ++ 11 dari versi sebelumnya tidak membuat masalah dengan tipe agregat, jadi perubahan ini tidak penting. Tetapi sekarang daftar di kawat gigi memiliki fitur baru. Meskipun tidak memiliki tipe, ini dapat disembunyikan dikonversi ke std::initializer_list
, itu adalah tipe baru yang khusus. Dan jika ada konstruktor yang menerima std::initializer_list
sebagai input, maka konstruktor ini disebut:
template <typename T> class vector {
Menurut saya, dari sisi komite C ++, std::initializer_list
bukan solusi yang paling sukses. Dari dia lebih banyak ruginya daripada kebaikan.
Untuk mulai dengan, std::initializer_list
adalah vektor ukuran tetap dengan elemen const
. Artinya, ini adalah tipe, ia memiliki fungsi begin
dan end
yang dikembalikan iterator, ia memiliki jenis iterator sendiri, dan untuk menggunakannya, Anda harus menyertakan header khusus. Karena elemen std::initializer_list
adalah const
, itu tidak dapat dipindahkan, oleh karena itu, jika T
dalam kode di atas adalah tipe move-only, kode tidak akan dieksekusi.
Selanjutnya, std::initializer_list
adalah sebuah objek. Dengan menggunakannya, kami, pada kenyataannya, membuat dan mentransfer objek. Sebagai aturan, kompiler dapat mengoptimalkan ini, tetapi dari sudut pandang semantik, kita masih berurusan dengan objek yang tidak perlu.
Beberapa bulan yang lalu ada jajak pendapat di Twitter: jika Anda bisa kembali ke masa lalu dan menghapus sesuatu dari C ++, apa yang akan Anda hapus? Kebanyakan dari semua suara menerima persis initializer_list
.
https://twitter.com/shafikyaghmour/status/1058031143935561728
, initializer_list
. , .
, . , initializer_list
, . :
std::vector<int> v(3, 0);
vector
int
, , , — . . , initializer_list
, 3 0.
:
std::string s(48, 'a');
48 «», «0». , string
initializer_list
. 48 , . ASCII 48 — «0». , , , int
char
. . , , .
. , ? ?
template <typename T, size_t N> auto test() { return std::vector<T>{N}; } int main () { return test<std::string, 3>().size(); }
, — 3. string
int
, 1, std::vector<std::int>
initializer_list
. initializer_list
, . string
int
float
, , . , . , emplace , . , {}
.
, .
.
— ( {a}
)
( = {a}
);
:
- «» ,
std::initializer_list
.
— . - ,
()
.
.
1: = {a}
, a
,
.
2: , {}
.
, initializer_list
.
Widget<int> widget{}\
?
template Typename<T> struct Widget { Widget(); Widget(std::initializer_list<T>); }; int main() { Widget<int> widget{};
, , initializer_list
, initializer_list
. . , , initializer_list
. , . , .
{}
. , -, , Widget() = default
Widget() {}
— .
Widget() = default
:
struct Widget { Widget() = default; int i; }; int main() { Widget widget{};
Widget() {}
:
struct Widget { Widget() {};
: , (narrowing conversions). int
double
, , :
int main() { int i{2.0};
, double
. C++11, , . :
struct Widget { int i; int j; }; int main() { Widget widget = {1.0, 0.0};
, , , , (brace elision). , , . , map
. map
, — :
std::map<std::string, std::int> my_map {{"abc", 0}, {"def", 1}};
, . :
std::vector<std::string> v1 {"abc", "def"};
, , initializer_list
. initializer_list
, , , . , . , .
initializer_list
— initializer_list
, . , const char*
. , string
, char
. . , , .
:
. braced-init-list . :
Widget<int> f1() { return {3, 0};
, , braced-init-list . braced-init-list , .
, . StackOverflow , . , . , , :
#include <iostream> struct A { A() {} A(const A&) {} }; struct B { B(const A&) {} }; void f(const A&) { std::cout << "A" << std::endl; } void f(const B&) { std::cout << "B" << std::endl; } int main() { A a; f( {a} ); // A f( {{a}} ); // ambiguous f( {{{a}}} ); // B f({{{{a}}}}); // no matching function }
++14
, C++11 . , , . C++14. , .
, ++11 direct member initializers, . , direct member initializers . ++14, direct member initializers:
struct Widget { int i = 0; int j = 0; }; Widget widget{1, 2};
, auto
. ++11 auto
braced-init-list, std::initializer_list
:
int i = 3;
: auto i{3}
, int
, std::initializer_list<int>
. ++14 , auto i{3}
int
. , . , auto i = {3}
std::initializer_list<int>
. , : int
, — initializer_list
.
auto i = 3;
, C++14 , , , , . , .
, ++14 :
, , std::initializer_list
.
std::initializer_list
move-only .
c , emplace
make_unique
.
, :
, , .
: assert(Widget(2,3))
, assert(Widget{2,3})
. , , , . , . .
C++
, ++.
int
, . . — , .
: , , std::initializer_list
, direct member initializers. , .
, é . .
struct Point { int x = 0; int y = 0; }; setPosition(Point{2, 3}); takeWidget(Widget{});
braced-init-list — .
setPosition({2, 3}); takeWidget({});
, , . , — , . , , , , , . , , initializer_list
. : , , .
:
= value
= {args}
= {}
:
std::initializer_list
- direct member initialisation (
(args)
)
{args}
{}
é
(args)
, (args)
vexing parse. . 2013 , , auto
. , : auto i;
— . , :
auto widget = Widget(2, 3);
, . , , vexing parse:
auto thingy = Thingy();
« auto» («almost always auto», AAA), ++11 ++14 , , , std::atomic<int>
:
auto count = std::atomic<int>(0);
, atomic . , , , , . ++17 , , (guaranteed copy elision):
auto count = std::atomic<int>(0);
auto
. — direct member initializers. auto
.
++17 CTAD (class template argument deduction). , . . , CppCon, CTAD , . , ++17 , ++11 ++14, , . , , , , .
(++20)
++20, . , , : (designated initialization):
struct Widget { int a; int b; int c; }; int main() { Widget widget{.a = 3, .c = 7}; };
, . , , . , .
, b
.
, , , . , .
, , 99, :
, , . ++ , , . :
Widget widget{.c = 7, .a = 3};
, .
++ , {.ce = 7};
, {.c{.e = 7}}
:
Widget widget{.ce = 7};
++ , , :
Widget widget{.a = 3, 7};
++ . , -, , .
int arr[3]{.[1] = 7};
C++20
++20 , . ( wg21.link/p1008 ).
++17 , , . , , , :
struct Widget { Widget() = delete; int i; int j; }; Widget widget1;
, , . ++20 . , . , . , , , .
( wg21.link/p1009 ). Braced-init-list new
, : , ? — , : braced-init-list new
:
double a[]{1, 2, 3};
, ++11 braced-init-list. ++ . , .
(C++20)
, ++20 . , . ++20 : ( wg21.link/p0960 ).
struct Widget { int i; int j; }; Widget widget(1, 2);
. , emplace
make_unique
. . : auto
, : 58.11 .
struct Widget { int i; int j; }; auto widget = Widget(1, 2);
, :
int arr[3](0, 1, 2);
, : uniform 2.0. . , , , , . — initializer_list
: , , — . , . , - , — . .
, . direct member initializers. auto
. direct member initializers — , . , . — , .
, , . — , — . , .

, , C++ Russia 2019 Piter «Type punning in modern C++» . , ++20, , , «» ++ , .