Topik ini membahas member initializer list, yaitu daftar inisialisasi member yang ditulis setelah titik dua pada constructor. Materinya mencakup sintaksnya, urutan inisialisasi yang ditentukan oleh deklarasi member dan bukan oleh urutan daftar, member yang wajib diinisialisasi lewat daftar, serta alasan inisialisasi lebih baik daripada penugasan di badan constructor. Setelah membacanya, pembaca dapat menulis constructor yang membangun member langsung dalam keadaan finalnya.
Sintaks member initializer list#
Member initializer list ditempatkan setelah tanda kurung parameter constructor, diawali tanda titik dua, dan berisi pasangan member(nilai) yang dipisahkan koma. Penulisan ini menyatakan bahwa member diinisialisasi, bukan diberi nilai setelah jadi. Kelas berikut memakainya untuk mengisi kedua member sekaligus pada saat dibangun.
#include <iostream>
class Kotak {
private:
double panjang;
double lebar;
public:
Kotak(double p, double l) : panjang(p), lebar(l) {
}
double luas() const {
return panjang * lebar;
}
};
int main() {
Kotak k(3.5, 2.0);
std::cout << "Luas = " << k.luas() << "\n";
return 0;
}Luas = 7Badan constructor pada kelas di atas memang kosong, dan kekosongan itu justru menunjukkan makna daftar inisialisasinya. Seluruh pekerjaan pemberian nilai awal selesai sebelum badan constructor dijalankan, sehingga tidak ada langkah penugasan tersisa. Inisialisasi dan penugasan memang terlihat serupa pada tipe sederhana seperti double, tetapi keduanya berbeda mekanisme, dan perbedaannya melebar pada tipe yang mengelola sumber daya sendiri.
Sebagai pembanding, constructor pada topik sebelumnya mengisi member di dalam badan dengan pernyataan penugasan. Pola itu menghasilkan objek yang benar, tetapi member sudah dibangun lebih dahulu dengan nilai awal default, kemudian digantikan nilainya. Daftar inisialisasi menghilangkan langkah tengah tersebut karena nilai akhirnya langsung dipasang pada saat member dibangun.
Urutan inisialisasi mengikuti urutan deklarasi#
Member selalu diinisialisasi mengikuti urutan deklarasinya di dalam kelas, apa pun urutan yang tertulis pada daftar inisialisasi. Aturan ini berlaku agar urutan penghancuran, yang merupakan kebalikannya, tetap konsisten tanpa perlu membaca setiap constructor. Program berikut memakai struct penanda yang mencetak labelnya saat dibangun, lalu menuliskan daftar inisialisasi dengan urutan yang sengaja dibedakan dari urutan deklarasinya.
#include <iostream>
#include <string>
struct Penanda {
Penanda(const std::string& label) {
std::cout << "Menginisialisasi " << label << "\n";
}
};
class Laporan {
private:
Penanda judul;
Penanda tanggal;
Penanda isi;
public:
Laporan()
: isi("isi"), judul("judul"), tanggal("tanggal") {
}
};
int main() {
std::cout << "Membuat objek Laporan\n";
Laporan laporan;
return 0;
}Membuat objek Laporan
Menginisialisasi judul
Menginisialisasi tanggal
Menginisialisasi isiDaftar inisialisasi menuliskan isi lebih dulu, tetapi keluaran menunjukkan judul, tanggal, dan isi yang diinisialisasi sesuai urutan deklarasinya. Compiler tidak menjalankan ulang kode untuk menyesuaikan urutan daftar, sehingga daftar yang urutannya tidak cocok hanya menyesatkan pembaca. Peringatan -Wreorder milik GCC melaporkan ketidakcocokan semacam ini secara eksplisit, dan peringatan tersebut aktif sebagai bagian dari -Wall.
Member yang wajib diinisialisasi lewat daftar#
Member const dan member berupa referensi tidak dapat diberi nilai melalui penugasan, karena penugasan berarti mengganti isi member yang sudah jadi, sedangkan keduanya harus terikat sejak pertama kali dibangun. Member semacam ini wajib diisi melalui daftar inisialisasi, dan constructor akan ditolak compiler apabila daftarnya tidak menyediakan nilai.
#include <iostream>
#include <string>
class Dokumen {
private:
const std::string judul;
std::string& kategori;
public:
Dokumen(const std::string& j, std::string& k) : judul(j), kategori(k) {
}
void tampilkan() const {
std::cout << "Judul: " << judul << ", kategori: " << kategori << "\n";
}
};
int main() {
std::string kategori = "catatan";
Dokumen d("Jadwal piket", kategori);
d.tampilkan();
kategori = "jadwal";
d.tampilkan();
return 0;
}Judul: Jadwal piket, kategori: catatan
Judul: Jadwal piket, kategori: jadwalMember judul bersifat const, sehingga isinya tetap untuk sepanjang masa hidup objeknya. Member kategori berupa referensi yang terikat pada variabel di luar objek, sehingga pergantian isi variabel tersebut terbaca melalui objeknya. Kedua sifat itu baru mungkin karena keduanya diikat lewat daftar inisialisasi, bukan diberi nilai di badan constructor.
// tidak-dijalankan (member const tidak dapat diberi nilai di badan constructor, sehingga kompilasi gagal)
class Lingkaran {
private:
const double pi = 3.14159;
double radius = 1.0;
public:
Lingkaran(double r) {
pi = 3.14159265358979;
radius = r;
}
};Mengapa inisialisasi lebih baik daripada penugasan#
Pada tipe seperti std::string atau std::vector, perbedaan inisialisasi dan penugasan berdampak langsung pada jumlah pekerjaan yang dilakukan program. Member yang diisi di badan constructor dibangun lebih dahulu sebagai objek kosong, kemudian penugasannya menyalin isi baru di atas objek yang sudah ada. Daftar inisialisasi membangunnya sekali saja dalam keadaan lengkap, sehingga satu langkah pembangunan dan satu penyalinan terhapus dari jalur eksekusi.
Efisiensi itu bukan satu-satunya alasannya. Penugasan di badan constructor hanya dilakukan apabila eksekusi benar-benar sampai ke badan tersebut, sedangkan daftar inisialisasi tetap dijalankan sebelum badan mana pun, termasuk ketika constructor berakhir lebih awal karena exception dari member lain. Member const dan referensi menuntut daftar inisialisasi secara mutlak, seperti ditunjukkan bagian sebelumnya. Karena tiga alasan tersebut, kebiasaan yang dianjurkan adalah menuliskan seluruh inisialisasi member pada daftar dan menyisakan badan constructor untuk pemeriksaan atau pekerjaan yang memang baru bisa dilakukan setelah member selesai dibangun.
Kesalahan umum#
Kesalahan berikut paling sering muncul saat memakai daftar inisialisasi.
- Mengurutkan daftar inisialisasi tanpa memeriksa urutan deklarasi member. Hasilnya daftar menyesatkan pembaca, dan bila ada member yang bergantung pada member lain, nilainya bisa berupa nilai yang belum diinisialisasi.
- Memberikan nilai member
constatau referensi di badan constructor. Compiler menolaknya, dan perbaikannya adalah memindahkan pengikatannya ke daftar inisialisasi. - Mengulang nama parameter dengan nama member tanpa sadar, misalnya
panjang(panjang). Bentuk ini sah dan memang dipakai, tetapi sekali salah baca, member dapat menginisialisasi dirinya sendiri dengan isinya sendiri, sehingga penamaan parameter sepertipdanllebih aman.
Ringkasan#
- Member initializer list menginisialisasi member langsung pada saat dibangun, bukan memberi nilai setelahnya.
- Urutan inisialisasi ditentukan urutan deklarasi member, sehingga urutan pada daftar yang tidak cocok hanya menyesatkan dan dilaporkan
-Wreorder. - Member
constdan member berupa referensi wajib diinisialisasi lewat daftar, karena penugasan terhadap keduanya tidak sah. - Inisialisasi menghindari pembangunan dua tahap pada tipe yang mengelola sumber daya, sehingga lebih ringkas dan lebih aman daripada penugasan di badan constructor.
Referensi#
- Constructors and member initializers — cppreference — Aturan member initializer list dan urutan inisialisasinya.
- Constructor member initializer lists — LearnCpp — Pembahasan sintaks daftar inisialisasi beserta urutan deklarasinya.
- Default member initialization — LearnCpp — Nilai awal member di dalam definisi kelas dan interaksinya dengan constructor.
- Warning Options — GCC online documentation
— Dokumentasi opsi
-Wreorderuntuk urutan inisialisasi yang tidak cocok.