Di semua constructor sebelumnya, kita mengisi data member lewat penugasan di dalam badan: id = i; pelanggan = nama;. Cara ini terasa wajar karena mirip variable biasa, tetapi sebenarnya ada dua langkah yang terjadi — member dibuat dulu dengan nilai bawaannya, lalu ditimpa. C++ menyediakan jalur yang lebih langsung: member initializer list, daftar inisialisasi yang ditulis setelah tanda titik dua pada constructor. Lebih dari sekadar gaya, daftar ini adalah satu-satunya jalur yang sah untuk member const dan reference, dan menyimpan satu aturan urutan yang sering mengejutkan.
Sintaks Member Initializer List #
Member initializer list ditulis setelah kurung parameter constructor, diawali titik dua, lalu tiap member ditulis dengan pola member(nilai), dipisah koma:
class Titik {
int x;
int y;
public:
Titik(int px, int py) : x(px), y(py) {}
};Konstruksi : x(px), y(py) berarti: buat x langsung dengan nilai px, buat y langsung dengan nilai py. Untuk tipe fundamental hasilnya sama saja dengan penugasan di badan; perbedaannya baru terasa pada member bertipe class seperti std::string, yang bisa dibangun langsung dari datanya tanpa tahap “buat kosong lalu timpa”. Efisiensi inilah alasan pertama memakai daftar ini.
Inisialisasi Langsung vs Penugasan #
Mari kita bandingkan dua versi constructor untuk member std::string. Dengan penugasan di badan — Pegawai() { nama = "(tanpa nama)"; } — compiler pertama membangun nama sebagai string kosong, kemudian menugaskannya dengan teks baru: dua operasi. Dengan daftar inisialisasi — Pegawai() : nama("(tanpa nama)") {} — string langsung dibangun dalam keadaan terisi: satu operasi. Untuk std::string kecil selisihnya nyaris tak terukur, tetapi pola yang sama berlaku untuk object besar, dan kebiasaan menulis daftar inisialisasi membuat kode kita tidak membuang biaya di tempat yang tidak perlu.
Ada pula perbedaan semantik yang lebih dalam: penugasan di badan constructor bukan inisialisasi, sehingga member yang sudah “terlanjur” dibuat harus punya state default yang sah. Untuk beberapa tipe — dan untuk dua kategori member berikutnya — state default itu bahkan tidak ada. Di titik itulah daftar inisialisasi berhenti menjadi pilihan gaya dan menjadi kewajiban.
Member const dan Reference: Satu-satunya Jalur #
Member bertipe const tidak boleh ditugaskan setelah dibuat — aturan yang sama berlaku ketika ia berada di dalam class. Member berupa reference juga tidak boleh “ditugaskan”: ia harus diikat sekali seumur hidupnya. Penugasan di badan constructor terlambat untuk keduanya, sehingga satu-satunya jalur yang sah adalah member initializer list. Compiler g++ 14.2 menolak kode yang melanggar dengan pesan seperti ini:
constmiss.cpp:6:5: error: uninitialized const member in 'const int' [-fpermissive]
6 | X(int i) {
| ^
constmiss.cpp:3:15: note: 'const int X::id' should be initialized
constmiss.cpp:7:12: error: assignment of read-only member 'X::id'Program berikut memakai keduanya secara benar. Artikel menyimpan id yang tidak boleh berubah dan reference ke string sumber yang diikat sejak konstruksi:
#include <iostream>
#include <string>
class Artikel {
private:
const int id; // member const: WAJIB diisi lewat initializer list
std::string& sumber; // member reference: juga WAJIB lewat initializer list
public:
// Satu-satunya cara memberi nilai pada id dan sumber:
// lewat member initializer list
Artikel(int i, std::string& sumberData) : id(i), sumber(sumberData) {}
void cetak() {
std::cout << "Artikel #" << id << " dari " << sumber << "\n";
}
};
int main() {
std::string sumber = "Arsip Perpustakaan";
Artikel artikel(7, sumber);
sumber = "Basis Data Nasional"; // perubahan langsung terlihat lewat reference
artikel.cetak();
return 0;
}Output yang Dihasilkan:
Artikel #7 dari Basis Data NasionalBedah singkatnya:
- Baris 6-7 (
const int id;danstd::string& sumber;): dua member yang tidak bisa diisi lewat penugasan.idbersifat read-only sejak dibuat, sedangkansumberhanyalah nama lain (alias) untuk string milik pihak lain. - Baris 12 (
Artikel(int i, std::string& sumberData) : id(i), sumber(sumberData) {}): member initializer list mengikat keduanya. Badan constructor bahkan kosong — semua pekerjaan selesai sebelum badan dimulai. - Baris 20-23 (
sumber = "Basis Data Nasional"; ...): kita mengubah string aslinya, bukan objectArtikel-nya. Karenasumberadalah reference, baris terakhir mencetak nilai baru — bukti bahwa reference benar-benar alias, bukan salinan.
Urutan Inisialisasi: Urutan Deklarasi, Bukan Urutan Daftar #
Aturan yang paling sering mengejutkan: member selalu diinisialisasi sesuai urutan deklarasinya di dalam class, bukan sesuai urutan penulisannya di member initializer list. Daftar yang menuliskan b lebih dulu tidak membuat b diisi lebih dulu. Program berikut membuktikannya dengan jejak cetak — dan sekaligus memperlihatkan bahwa compiler menandai daftar yang urutannya menyesatkan:
#include <iostream>
// Fungsi bantu: mencetak jejak setiap kali dipanggil, agar kita bisa
// melihat urutan inisialisasi member secara nyata.
int lacak(const char* nama) {
std::cout << "mengisi " << nama << "\n";
return 42;
}
class Urutan {
private:
int a; // dideklarasikan PERTAMA -> selalu diinisialisasi lebih dulu
int b; // dideklarasikan KEDUA -> selalu diinisialisasi belakangan
public:
// Urutan di daftar sengaja dibalik: b ditulis dulu, baru a
Urutan() : b(lacak("b")), a(lacak("a")) {}
void cetak() {
std::cout << "a = " << a << ", b = " << b << "\n";
}
};
int main() {
Urutan u;
u.cetak();
return 0;
}Output yang Dihasilkan:
mengisi a
mengisi b
a = 42, b = 42Meskipun daftar menuliskan b(lacak("b")) di posisi pertama, output menunjukkan mengisi a tercetak lebih dulu. Urutan eksekusi ditentukan baris 11-12 — urutan deklarasi member — dan tidak bisa diubah oleh siapa pun. Menariknya, compiler juga mengingatkan kita; g++ 14.2 memberi peringatan -Wreorder untuk constructor di atas:
urutan-inisialisasi.cpp: In constructor 'Urutan::Urutan()':
urutan-inisialisasi.cpp:13:9: warning: 'Urutan::b' will be initialized after [-Wreorder]
13 | int b; // dideklarasikan KEDUA -> selalu diinisialisasi belakangan
urutan-inisialisasi.cpp:12:9: warning: 'int Urutan::a' [-Wreorder]
urutan-inisialisasi.cpp:17:5: warning: when initialized here [-Wreorder]Peringatan pada contoh ini sengaja kita biarkan sebagai bukti — pada kode nyata, tulislah daftar inisialisasi mengikuti urutan deklarasi supaya peringatan tidak muncul. Kebiasaan ini penting karena ada kasus di mana urutan benar-benar menentukan hasil: member yang diinisialisasi memakai member lain harus ditulis belakangan secara deklarasi, bila tidak, ia membaca nilai yang belum diisi.
Daftar inisialisasi menyelesaikan duplikasi antar overload constructor? Belum sepenuhnya — tiap overload masih menulis daftarnya sendiri. Chapter berikutnya menyempurnakannya dengan delegating constructor: satu constructor yang menyerahkan seluruh pekerjaan inisialisasi ke constructor lain.
Rujukan resmi: Member initializer list (cppreference) , Data members (cppreference) .