Virtual function di class dasar adalah undangan; overriding adalah jawabannya — class turunan menulis versi sendiri dari method tersebut, dan versi itulah yang dijalankan saat object dipanggil lewat pointer atau referensi dasar. Mekanisme ini terlihat sepele, tetapi ia penuh jebakan kecil yang baru ketahuan saat runtime: salah satu karakter di nama method, const yang lupa, atau parameter yang sedikit berbeda — semuanya bukan overriding, melainkan method baru yang membungkam versi dasar. C++ modern memberi kita dua kata kunci untuk mengunci kebenarannya: override yang memaksa compiler memeriksa, dan final yang menyatakan “sampai di sini, tidak ada yang boleh menulis ulang lagi”. Chapter ini membedah keduanya serta bedah tuntas masalah hiding.
Aturan Override: Signature Harus Sama Persis #
Versi turunan disebut melakukan overriding hanya bila signature-nya identik dengan versi dasar: nama method, daftar parameter, dan qualifier const — semuanya harus cocok. Return type juga wajib sama atau kompatibel (covariant, topik lanjutan yang jarang dibutuhkan di awal). Bila cocok dan method dasar virtual, versi turunan menggantikan versi dasar pada dynamic dispatch; virtualitas juga otomatis mengalir ke turunan tanpa menulis ulang virtual.
Satu-satunya detail yang boleh berbeda adalah isi badan method. Signature adalah kontrak yang tidak bisa ditawar — dan di sinilah kesalahan paling umum menunggu: void bersuara() yang lupa const berbeda dari void bersuara() const. Keduanya tampak serupa bagi mata manusia, tetapi bagi compiler itu dua method berbeda. Tanpa alat pemeriksa, salah satu bentuk ini lolos kompilasi dan diam-diam tidak pernah ikut dynamic dispatch.
override: Biarkan Compiler Memeriksa #
Keyword override ditulis di akhir deklarasi method turunan sebagai pernyataan tegas: “method ini dimaksudkan untuk menulis ulang virtual function”. Compiler kemudian memverifikasi klaim itu terhadap class dasar — dan bila ternyata tidak ada yang ditulis ulang, kompilasi gagal seketika. Inilah nilainya: kesalahan yang tadinya bersembunyi sampai runtime kini ditangkap di waktu kompilasi, tepat di baris yang salah.
Lihat dua kesalahan klasik yang keduanya tertangkap oleh override. Pertama, salah ketik nama — kita menulis bersuaraa dengan dua a:
error: 'void Kucing::bersuaraa() const' marked 'override', but does not override
void bersuaraa() const override {}
^~~~~~~~~Kedua, const yang hilang — signature berbeda, sehingga bukan overriding:
error: 'void Kucing::bersuara()' marked 'override', but does not override
void bersuara() override {}
^~~~~~~~Tanpa override, kedua program itu lolos kompilasi tanpa suara — dan setiap panggilan lewat referensi dasar diam-diam menjalankan versi dasar. Dengan override, keduanya berhenti di baris yang tepat. Karena itu kebiasaan modern yang disarankan: setiap method turunan yang memang menulis ulang virtual function wajib ditandai override.
Contoh Lengkap: Override Berlapis #
Program berikut memakai override di setiap tingkat hierarki, plus final pada class agar rantai inheritance berhenti di tempat yang kita pilih:
#include <iostream>
class Hewan {
public:
virtual void bersuara() const {
std::cout << "Hewan mengeluarkan suara\n";
}
virtual void makan() const {
std::cout << "Hewan makan\n";
}
};
class Kucing : public Hewan {
public:
void bersuara() const override { // override: minta compiler memeriksa
std::cout << "Meong\n";
}
void makan() const override {
std::cout << "Kucing makan ikan\n";
}
};
class KucingAnggora final : public Kucing { // final pada class: tidak boleh diwarisi lagi
public:
void bersuara() const override {
std::cout << "Meooong~\n";
}
};
int main() {
KucingAnggora anggora;
const Hewan& h = anggora; // lewat referensi dasar
h.bersuara(); // versi paling turunan yang menang
h.makan(); // versi Kucing (dari class nenek moyang)
return 0;
}Output yang Dihasilkan:
Meooong~
Kucing makan ikanMari kita bedah:
- Baris 15 dan 18 (
overridediKucing): kedua method menulis ulang virtual function dariHewan. Penandaoverridememastikan signature-nya benar-benar cocok — bila kita lupaconstdi salah satunya, compiler berhenti di baris itu. - Baris 23 (
KucingAnggora final : public Kucing):finalyang ditempel pada class menyatakan tidak ada class lain boleh mewarisiKucingAnggora. Percobaanclass KucingPersia : public KucingAnggoraditolak compiler:error: cannot derive from 'final' base 'KucingAnggora' in derived type 'KucingPersia'. - Baris 25 (
overridediKucingAnggora): class paling turunan ikut menulis ulangbersuara(). Dynamic dispatch memilih versi paling turunan yang tersedia — bukan versi dasar, bukan pula versi tingkat tengah. - Baris 32-34 (referensi dasar):
hbertipeconst Hewan&, tetapi pemanggilannya menjawab versiKucingAnggorauntukbersuara()dan versiKucinguntukmakan(). Dua virtual function yang berbeda rantai override-nya bekerja independen — itulah dinamika yang bisa kita andalkan.
final juga bisa ditempel pada method, bukan hanya class: void bersuara() const override final; di Kucing membuat semua turunan di bawahnya dilarang menulis ulang method itu. Bila KucingAnggora mencoba, compiler menolaknya:
error: virtual function 'virtual void KucingAnggora::bersuara() const' overriding final function
note: overridden function is 'virtual void Kucing::bersuara() const'Hiding: Ketika Bukan Overriding #
Signature yang tidak cocok tidak menimbulkan error — justru itu jebakannya. Method turunan dengan nama sama tetapi signature berbeda melakukan hiding: ia menyembunyikan seluruh versi dasar dari pandangan object turunan, tanpa ikut dynamic dispatch sama sekali. Program berikut sengaja memperlihatkan celah itu (const sengaja dihilangkan) — compiler g++ 14.2 memberi peringatan yang justru layak kita kutip sebagai bukti:
#include <iostream>
class Hewan {
public:
virtual void bersuara() const {
std::cout << "Hewan mengeluarkan suara\n";
}
};
class Kucing : public Hewan {
public:
// Signature BERBEDA (const hilang): hiding, bukan overriding
void bersuara() {
std::cout << "Meong versi non-const\n";
}
};
int main() {
Kucing kucing;
kucing.bersuara(); // object non-const: versi Kucing terpilih
const Hewan& h = kucing; // lewat referensi dasar yang const
h.bersuara(); // signature tidak cocok: versi Hewan terpilih
return 0;
}Output yang Dihasilkan:
Meong versi non-const
Hewan mengeluarkan suaraBaris pertama output menunjukkan kucing.bersuara() memilih versi non-const milik Kucing; baris kedua menunjukkan panggilan lewat const Hewan& menjatuhkan pilihan ke versi Hewan — dua method yang tidak pernah bertemu. Compiler memperingatkan kejadian ini bila flag peringatannya aktif:
warning: 'virtual void Hewan::bersuara() const' was hidden [-Woverloaded-virtual=]
note: by 'void Kucing::bersuara()'Pola penutupnya sederhana namun kuat: nama sama tanpa override = dua dunia terpisah; nama sama dengan override = satu rantai dynamic dispatch yang jujur. Dengan alat pemeriksa ini di tangan, kita siap melangkah ke tujuan besar seluruh mekanisme: polymorphism, satu antarmuka yang melayani banyak perilaku sekaligus.
Rujukan resmi: Override specifier (cppreference) , Final specifier (cppreference) , Virtual specifier (cppreference) .