Constructor tanpa parameter yang bisa dipanggil tanpa satu pun argumen disebut default constructor, dan ia punya aturan main yang unik: kadang ada walau kita tidak menulisnya, dan kadang menghilang walau kita tidak menghapusnya. Memahami kapan compiler membuat default constructor secara otomatis adalah salah satu pengetahuan paling praktis dalam OOP C++, karena error “tidak bisa membuat object” hampir selalu bermula dari sini. Chapter ini membongkar tiga sumber default constructor dan satu bahaya yang menyertainya.

Kapan Compiler Membuatnya Otomatis #

Aturannya sederhana: bila kita tidak mendeklarasikan constructor apa pun, compiler membangkitkan default constructor publik secara diam-diam. Constructor buatan compiler ini tidak menulis apa pun ke data member, tetapi tetap menjalankan mekanisme inisialisasi standar — termasuk in-class member initializer yang kita bahas sebelumnya. Itulah sebabnya class Timer { int detik = 0; }; menghasilkan object bernilai 0 meski kita tidak menulis satu baris constructor pun.

Sebaliknya, begitu kita mendeklarasikan constructor lain — misalnya yang berparameter — compiler berhenti membangkitkan default constructor. Logikanya: kalian sudah mengambil alih urusan konstruksi, saya mundur. Akibatnya, Produk p; yang tadinya sah menjadi error karena tidak ada lagi constructor yang cocok dipanggil tanpa argumen. Inilah jebakan klasik: menambah constructor berparameter “secara iseng” langsung mematahkan semua kode yang membuat object tanpa argumen.

Tiga Cara Memperoleh Default Constructor #

Kita bisa memperoleh default constructor lewat tiga jalur, dan program contoh di bagian akhir memakai ketiganya:

  • Implicit: tidak mendeklarasikan constructor sama sekali; compiler membangkitkannya sendiri. Cocok untuk class sederhana yang mengandalkan in-class initializer.
  • Eksplisit dengan badan: kita menulis NamaClass() { ... } berisi logika pengisian. Cocok bila inisialisasi butuh perhitungan atau cetak.
  • Eksplisit dengan = default: kita menulis NamaClass() = default; untuk meminta compiler membangkitkan versi standarnya meski kita menyentuh deklarasi itu.

Bentuk = default berguna dalam dua situasi. Pertama, gaya penulisan: deklarasi = default terbaca sebagai pernyataan eksplisit “default constructor ini memang disengaja ada”, bukan kelalaian. Kedua, ketepatan teknis: = default adalah cara memulihkan default constructor setelah compiler berhenti membangkitkannya, tanpa kita harus menulis ulang logika pengisiannya. Untuk saat ini, anggap ia sebagai tombol “nyalakan lagi” yang jujur dan tidak menambah kode.

Bahaya: Member Tanpa Nilai Awal #

Default constructor buatan compiler tidak mengisi data member bertipe fundamental. Bila member tidak punya in-class initializer dan tidak diisi constructor, nilainya tak tentu (indeterminate) — bisa 0, bisa angka sampah dari sisa memori, dan angka itu bisa berbeda antar eksekusi. Membaca member semacam itu adalah undefined behavior, dan program kita tidak punya dasar apa pun untuk mengandalkannya.

Contoh kode berikut sengaja salah dan tidak boleh ditiru:

cpp
class A {
public:
    int x;      // tidak ada in-class initializer
    A() {}      // default constructor kosong: x tidak diisi
};

int main() {
    A a;
    std::cout << a.x << "\n";   // membaca nilai tak tentu!
    return 0;
}

Compiler yang berbaik hati memberi peringatan pada kode di atas. Dengan -O2, g++ 14.2 melaporkannya seperti ini:

text
uninit.cpp:9:20: warning: 'a.A::x' is used uninitialized [-Wuninitialized]
    9 |     std::cout << a.x << "\n";
      |                    ^
uninit.cpp:8:7: note: 'a' declared here
    8 |     A a;
      |       ^

Karena peringatan bukan jaminan (versi compiler lain bisa diam), disiplin kita harus dari sisi kode: setiap data member fundamental diberi in-class initializer, atau diisi oleh constructor. Karena itu pula contoh-contoh di Section ini selalu menulis = 0 pada deklarasi member angka.

Contoh: Tiga Sumber Default Constructor dalam Satu Program #

Program berikut memuat tiga class yang masing-masing memperoleh default constructor lewat jalur berbeda, lalu mencetak isinya:

cpp
#include <iostream>
#include <string>

// Kasus 1: tanpa constructor apa pun.
// Compiler otomatis membuat default constructor dan menjalankan
// in-class initializer di bawah.
class Timer {
public:
    int detik = 0;
    std::string nama = "tanpa nama";
};

// Kasus 2: default constructor ditulis eksplisit.
class Konfigurasi {
public:
    int jumlahLevel;
    bool fullscreen;

    Konfigurasi() {
        jumlahLevel = 10;
        fullscreen = false;
    }
};

// Kasus 3: kita menulis deklarasi kosong, lalu memaksa compiler
// membuat default constructor dengan "= default".
class Pengaturan {
public:
    int volume = 50;
    bool modeGelap = true;

    Pengaturan() = default;
};

int main() {
    Timer t;              // memanggil default constructor buatan compiler
    std::cout << t.detik << " " << t.nama << "\n";

    Konfigurasi konfig;   // memanggil default constructor eksplisit
    std::cout << konfig.jumlahLevel << " " << konfig.fullscreen << "\n";

    Pengaturan setelan;   // memanggil default constructor hasil "= default"
    std::cout << setelan.volume << " " << setelan.modeGelap << "\n";

    return 0;
}

Output yang Dihasilkan:

text
0 tanpa nama
10 0
50 1

Bedahnya:

  1. Baris 8-13 (class Timer): tidak ada constructor yang ditulis, tetapi Timer t; sah. Compiler membangkitkan default constructor yang menjalankan in-class initializer, sehingga tercetak 0 tanpa nama.
  2. Baris 16-27 (class Konfigurasi): default constructor eksplisit mengisi jumlahLevel dan fullscreen di badannya — satu-satunya sumber nilai untuk kedua member karena keduanya tidak punya in-class initializer.
  3. Baris 32-39 (class Pengaturan): Pengaturan() = default; meminta versi buatan compiler, dan versi itu tetap menjalankan in-class initializer: 50 dan 1.
  4. Baris 42-48 (main()): ketiga object dibuat dengan sintaks yang sama persis — NamaClass nama; — walau jalur konstruksinya berbeda. Bagi pemakai class, ketiganya tak terbedakan.

Lalu apa yang terjadi bila default constructor hilang? Kita buktikan dengan class yang hanya punya constructor berparameter, lalu dibuat tanpa argumen:

cpp
Produk p;   // class Produk hanya punya Produk(const std::string&)
text
nocall.cpp:8:12: error: no matching function for call to 'Produk::Produk()'
    8 |     Produk p;
      |            ^
nocall.cpp:5:5: note: candidate: 'Produk::Produk(const std::string&)'
    5 |     Produk(const std::string& n) { nama = n; }
      |              ^
nocall.cpp:5:5: note:   candidate expects 1 argument, 0 provided

Pesan no matching function for call to 'Produk::Produk()' berarti compiler mencari constructor yang bisa dipanggil tanpa argumen dan tidak menemukannya. Solusinya dua pilihan: selalu membuat object dengan argumen, atau memulihkan default constructor dengan Produk() = default;. Setelah class memakai std::string seperti di sini, nama tetap aman — string mengurus dirinya sendiri dan mulai kosong secara otomatis.

Kita kini menguasai pembuatan object tanpa argumen. Chapter berikutnya melangkah ke arah sebaliknya: constructor dengan parameter penuh, overloading-nya, dan dua sintaks pembuatan object yang tersedia di C++.

Rujukan resmi: Default constructors (cppreference) , Default initialization (cppreference) .