- Inisialisasi objek di C++ dapat bercabang menjadi default/value/list/aggregate initialization meskipun sintaksnya mirip seperti
T t;, T t{};, T t{...}, sehingga keadaan anggota yang sebenarnya bisa berbeda
- Status zero-initialization dapat berubah tergantung di mana konstruktor default ditulis dengan
= default; jika didefinisikan di luar kelas, anggota bisa tetap berisi nilai yang tidak diinisialisasi
- Bahkan tipe yang tampak seperti akan menghapus konstruktor default karena memiliki anggota
const int pun, jika merupakan aggregate, dapat diinisialisasi menjadi 0 melalui aggregate initialization dengan A a{};
- Inisialisasi aggregate berbasis tanda kurung di C++20 lebih permisif daripada inisialisasi dengan kurung kurawal, sehingga menimbulkan perbedaan seperti narrowing conversion dan dangling reference
- Dalam praktik, lebih aman menuliskan konstruktor eksplisit untuk menunjukkan niat inisialisasi daripada bergantung pada kombinasi konstruktor implisit dan aturan inisialisasi
Titik saat sintaks inisialisasi C++ bercabang
T t; melakukan default-initialization
- Jika
T adalah tipe kelas dan memiliki konstruktor default, konstruktor itu dijalankan
- Jika
T adalah array, setiap elemennya di-default-initialize
- Selain itu, tidak melakukan apa pun
T t{}; tampak seperti value-initialization, tetapi karena sintaks kurung kurawal, terlebih dahulu diproses sebagai list-initialization
- Bahkan pada contoh sederhana, perbedaannya langsung terlihat
int x{}; di-value-initialize sehingga diinisialisasi menjadi 0
int y; di-default-initialize sehingga nilainya tidak diinisialisasi
Pair p; dan Pair q{}; yang memiliki konstruktor default akan memanggil konstruktor
SimplePair r; yang hanya memiliki anggota akan di-default-initialize sehingga anggotanya bisa tidak terinisialisasi
Hasil yang berubah karena lokasi deklarasi konstruktor default
- Jika pengguna tidak mendeklarasikan konstruktor, kompiler akan mendeklarasikan konstruktor default implicitly-declared
- Jika
T() = default; ditulis pada deklarasi pertama di dalam kelas, menurut standar ia diperlakukan hampir sama dengan konstruktor yang dideklarasikan implisit
- Jika konstruktor default yang dideklarasikan implisit atau di-
default-kan secara eksplisit tidak dihapus, kompiler akan menyediakan implicitly-defined default constructor
- Dalam implementasi, ini harus setara dengan
T() {} yang memiliki badan kosong dan daftar inisialisasi anggota kosong
- Pada kode berikut,
t.x akan menjadi 0
struct T {
int x;
T() = default;
};
T t{};
std::cout << t.x << std::endl;
- Ini karena
t di-value-initialize, dan konstruktor default T bukan user-provided, sehingga zero-initialization terjadi terlebih dahulu sebelum konstruktor default dipanggil
= default di luar kelas dan konstruktor user-provided
= default yang tampak sama pun, jika didefinisikan di luar kelas, menjadi konstruktor user-provided
struct T {
int x;
T();
};
T::T() = default;
T t{};
std::cout << t.x << std::endl;
- Dalam kasus ini,
T::T() = default; bukan di-default-kan pada deklarasi pertama, melainkan konstruktor yang didefinisikan dengan default di luar kelas
- Aturan value-initialization menyatakan bahwa jika ada konstruktor default yang user-provided atau deleted, maka yang dilakukan adalah default-initialization
- Karena itu, pada contoh di atas hanya konstruktor default yang dijalankan tanpa zero-initialization, dan karena konstruktor tersebut tidak melakukan apa pun,
t.x menjadi nilai sampah
Saat konstruktor default dihapus
- Jika kompiler tidak dapat membuat konstruktor default yang masuk akal, konstruktor default yang dideklarasikan implisit dapat didefinisikan sebagai deleted
- Kondisi yang umum antara lain sebagai berikut
- Ada anggota referensi non-statis
- Ada anggota non-statis atau kelas dasar non-abstrak yang tidak tepat untuk default-construct atau destruct
- Ada anggota non-statis
const tanpa default member initializer, dan anggota tersebut bukan const-default-constructible
- Jika pengguna menyediakan konstruktor secara langsung, kompiler tidak akan mencoba mendefinisikan konstruktor default implisit, dan juga tidak akan membuat konstruktor deleted
- Bahkan dengan kondisi seperti ini, aturan inisialisasi C++ secara keseluruhan tetap sangat permisif
Saat aggregate initialization ikut masuk
struct A { const int x; }; A a{}; tampak seperti seharusnya gagal dikompilasi karena konstruktor default dihapus, tetapi kenyataannya bisa dikompilasi dan a.x menjadi 0
- Kuncinya adalah bahwa
A merupakan aggregate
- Aggregate adalah array, atau kelas yang memenuhi syarat berikut
- Tidak memiliki konstruktor user-declared atau inherited
- Tidak memiliki anggota data non-statis langsung yang private/protected
- Tidak memiliki kelas dasar langsung yang private/protected
- Tidak memiliki fungsi virtual atau kelas dasar virtual
- Jika list-initialization dilakukan pada aggregate, maka kecuali beberapa pengecualian tertentu, akan diterapkan aggregate initialization
- Setiap elemen dalam daftar inisialisasi menginisialisasi setiap elemen aggregate secara berurutan
- Jika jumlah elemen daftar kurang, elemen sisanya diinisialisasi dengan default member initializer bila ada
- Jika tidak ada default member initializer dan bukan referensi, elemen tersebut di-copy-initialize dengan daftar inisialisasi kosong
- Karena itu,
A a{}; bukan pemanggilan konstruktor, melainkan kasus di mana a.x di-copy-list-initialize dengan daftar kosong lalu melalui value-initialization dan akhirnya zero-initialization
Aturan tambahan pada inisialisasi dengan kurung kurawal
- Sintaks berbasis kurung kurawal seperti
T t{...} dan T t = {...} pada umumnya adalah list-initialization
T t{...} adalah direct-list-initialization
T t = {...} adalah copy-list-initialization
- Pada tipe kelas non-aggregate, jika daftar inisialisasi kosong dan ada konstruktor default, maka dilakukan value-initialization
- Selain itu, konstruktor dipertimbangkan melalui overload resolution biasa, dan overload konstruktor
std::initializer_list mendapat prioritas
- Inisialisasi dengan kurung kurawal tidak mengizinkan narrowing conversion saat menginisialisasi dengan satu elemen
struct A {
const int x;
};
A b{4}; // bisa
A c{4.0f}; // error narrowing conversion
Inisialisasi dengan tanda kurung lebih permisif
- Inisialisasi dengan tanda kurung seperti
T t(a, b, c); memanggil direct-non-list-initialization, yang mirip dengan direct-list-initialization tetapi memiliki aturan berbeda
T t(); bukan pembuatan objek, melainkan diartikan sebagai deklarasi fungsi tanpa argumen dengan tipe kembalian T, yaitu masalah most vexing parse
- Sejak C++20, aggregate juga dapat menggunakan inisialisasi dengan tanda kurung
- Aggregate initialization berbasis tanda kurung bekerja berbeda dari inisialisasi berbasis kurung kurawal
- Mengizinkan narrowing conversion
- Elemen yang tersisa tidak diinisialisasi dengan daftar kosong, melainkan langsung di-value-initialize
- Tidak memperpanjang masa hidup objek sementara yang di-bind ke referensi
struct T {
const int& r;
};
T t(42);
- Pada kode di atas,
t.r menjadi dangling reference, dan membacanya adalah undefined behaviour
std::initializer_list, copy constructor, dan elision
- Inisialisasi dengan tanda kurung dapat memanggil copy constructor
T bahkan ketika ada overload konstruktor std::initializer_list<T>
- Sebaliknya, pada inisialisasi dengan kurung kurawal, konstruktor
std::initializer_list bisa diprioritaskan
struct T {
T(std::initializer_list<T>) {
std::cout << "list" << std::endl;
}
T(const T&) {
std::cout << "copy" << std::endl;
}
};
T t{}; // list
T s{t}; // list
T r(t); // copy
T q(T{}); // list, tanpa copy
- Pada
T q(T{});, tidak terjadi penyalinan karena dalam direct- dan copy-initialization, jika initializer adalah prvalue bertipe T, objek diinisialisasi langsung dari ekspresi tersebut
- Perilaku ini biasanya dikenal sebagai copy elision, tetapi standar tidak secara eksplisit menyebutnya sebagai elision
- Apakah elision yang sama dimungkinkan pada list-initialization masih kurang dijelaskan dalam standar, dan CWG issue 2311 berkaitan dengan hal ini
- GCC dan Clang melakukan elision dalam sebagian besar kasus
- Jika ada overload konstruktor
std::initializer_list<T>, GCC menggunakan konstruktor itu alih-alih melakukan elision
Urutan evaluasi dan kesimpulan praktis
- Urutan evaluasi elemen dalam daftar inisialisasi bertanda kurung tidak dijamin
- Daftar inisialisasi dengan kurung kurawal mengevaluasi elemen secara ketat dari kiri ke kanan
- Ada juga aturan khusus seperti inisialisasi variabel statis dan constant initialization, tetapi hanya dari inisialisasi objek biasa saja aturannya sudah cukup kompleks
- Dalam praktik, lebih aman menulis konstruktor eksplisit untuk menyatakan niat inisialisasi daripada bergantung pada konstruktor default implisit dan aturan inisialisasi
1 komentar
Opini Hacker News
Penjelasan bahwa jika
Tdiinisialisasi sebagai value initialization, hasilnya menjadi 0 memang benar; awalnya saya melewatkannya, tetapi penulis sebenarnya melakukan value initialization padaxSecara keseluruhan, aturan inisialisasi C++ telah mengalami perluasan dan perbaikan selama 40 tahun sehingga ada bagian-bagian yang rumit dan tidak intuitif, tetapi menurut saya sekitar 99,9% tetap bekerja sesuai harapan
Perbaikan besar menurut saya adalah membuat default initialization menjadi eksplisit, dan selain itu selalu melakukan value initialization. Kalau ingin menghindari biaya mengisi array besar dengan 0, akan lebih baik jika itu dinyatakan eksplisit dengan sintaks seperti
std::array = void;Ini terlihat seperti kasus seseorang yang terlalu ingin pintar lalu mengeluh karena sulit ditangani
Seluruh pendekatannya keliru. Jangan menaruh referensi const di dalam struct; kalau memang perlu, lebih tepat memakai
std::reference_wrapperJawabannya memang agak ketus, tetapi intinya kesimpulan tulisan itu sepenuhnya salah. Jangan menulis konstruktor sendiri, ikuti aturan 5/3/0, dan jika harus menyimpan referensi const, pastikan apakah Anda sedang meneruskan objek temporer rvalue. Ini bukan masalah yang semenakutkan itu
Bahkan orang yang hanya pernah mengikuti tutorial dasar pun biasanya akan menghindari skenario buatan seperti ini
std::reference_wrapper, dan juga belum pernah melihatnya di berbagai codebase C++ tempat saya bekerjaMungkin dipakai di suatu tempat dalam kode template yang dalam dan kompleks, tetapi meskipun pernyataan itu bisa benar, menurut pengalaman saya ini bukan pengetahuan umum
Teks asli
I Have No Mouth, and I Must Scream (1967)bisa dilihat di sini: https://talesofmytery.blogspot.com/2018/10/harlan-ellison-i-...Keluarga Martin H Greenberg sedang menginap di rumah kami, dan dia mencari Martin. Saya suka Sci-Fi dan sudah membaca beberapa bukunya, tetapi setelah mendengar namanya, percakapan itu hampir hanya saya ingat samar-samar. Akhirnya saya membangunkan Martin dan menyerahkan telepon, lalu setelah itu sulit tidur
Saya terkejut tidak ada komentar sinis tentang bagian
unlessyang bahkan dicetak tebalAda sintaks bagus seperti
T t{v0};danT t{v0, v1};, tetapi inisialisasi dengan 1 elemen tidak bekerja seandal kasus dengan 2 elemen atau lebihIni terasa aneh dalam bahasa yang bergerak ke arah memudahkan pembuatan struct dari parameter pack, dan juga mendukung sesuatu mirip array panjang variabel yang bisa diinisialisasi seperti ini. Tipenya tentu saja juga bisa berupa template
Anda bisa menulis konstruktor sendiri, dan Anda juga bisa menginisialisasi tuple atau array dengan hanya menyediakan satu elemen, tetapi dalam kasus khusus, konstruktor yang salah bisa dipanggil. Saat initialization list C++11 baru muncul, saya menemukan hal ini dan menganggapnya gila
Untuk melihat lebih banyak keanehan C++, saya merekomendasikan C++ FQA: https://yosefk.com/c++fqa/
Sekarang sudah sekitar 15 tahun usianya, tetapi karena C++ hampir tidak pernah menghapus fitur atau perilaku lama, isinya sekaligus tidak lekang waktu
Tema blognya benar-benar indah. Jelas terlihat terinspirasi dari komputer era DEC, tetapi tetap bersih dan minimalis sehingga terasa segar
Mengerikan. Ini salah satu bagian menyeramkan dari C++. Sayang sekali, karena bahasa ini juga punya fitur-fitur keren dan dikerjakan oleh orang-orang hebat
Saya berharap sesuatu seperti C++ syntax 2 dari Herb bisa menjadikannya bahasa yang dapat dipakai oleh orang biasa seperti saya
Contoh-contoh dalam tulisan itu dibuat untuk sengaja menyentuh edge case dari fitur bahasa yang secara luar biasa otomatis menghasilkan special member function yang dipilih sendiri untuk tidak ditulis oleh programmer
C++ punya tujuan desain “bayar hanya untuk yang Anda pakai”, dan rule of three/rule of five adalah pengetahuan dasar tingkat pemula di C++. Special member function semacam ini hanya dihasilkan compiler dalam kondisi tertentu, jadi fakta bahwa ia tidak otomatis dibuat ketika kondisinya tidak sesuai juga termasuk hal dasar
Pada akhirnya, saya tidak tahu apakah mengetahui bahwa Anda harus mengatur sendiri konstruktor yang akan dipakai itu begitu tidak masuk akal. Fakta bahwa Anda harus menginisialisasi sesuatu saat membuat instans juga bukan hal yang mengejutkan. Di dunia nyata, orang-orang menyelesaikan pekerjaan nyata dengan cara seperti ini tanpa keributan besar
Membaca ini bikin pusing. Jadi teringat masa ketika mencoba memahami konstruktor Java dan inisialisasi objek
Setidaknya dari pengalaman sejauh ini, pilihan seperti Go dan Rust yang tidak punya konstruktor khusus membuat banyak hal jadi lebih sederhana
Penasaran apakah ada orang yang setelah pindah ke bahasa tanpa konstruktor jadi merindukan konstruktor
Tentu saja kita bisa menulis tiap field secara manual ke
MaybeUninitdenganptr::write, tetapi itu tidak ergonomis dan struct harus mengizinkannya secara eksplisitPada akhirnya itu berarti semua konstruktor harus dideklarasikan dan didefinisikan secara eksplisit, dan C++ pun sejak awal menyediakan pilihan itu. Pembuatan otomatis special member function adalah fitur yang ditambahkan agar hanya bekerja pada kasus-kasus yang sangat spesifik bagi orang yang ingin menghindari kode repetitif
Kalau mau, tambahkan saja konstruktor sendiri dan hidup seperti biasa. Mengeluh bahwa hal sederhana jadi rumit karena special member function itu mirip mengeluh bahwa bahasa Inggris tidak sederhana karena ada kata-kata sulit di kamus
Sepemahaman saya, konstruktor Rust pada dasarnya mirip Java, bukan?
https://codefibershq.com/blog/golang-why-nil-is-not-always-n...
Penasaran apakah ada alat C++ yang menambahkan atau menampilkan semua perilaku implisit yang terjadi di balik layar
Misalnya alat yang menunjukkan konstruktor yang ditambahkan otomatis, copy constructor implisit, dan berbagai hal mengejutkan lainnya
cppinsightsadalah yang terbaikUntuk
T::T() = default;, contoh bahwa kita mungkin mengharapkan output-nya 0 tetapi ternyata menjadi nilai sampah sebenarnya tidak terlalu anehTautan terkait: https://consteval.ca/2024/07/03/initialization/#:~:text=You%...
Karena jika dibuat berbeda, siapa pun pengguna library bisa mengubah perilaku library tersebut