Kata polymorphism berasal dari bahasa Yunani: “banyak bentuk”. Dalam pemrograman berorientasi objek, ia berarti satu antarmuka yang sama melayani banyak perilaku — kode pemanggil berbicara dengan Bentuk, sementara object yang di baliknya memutuskan sendiri cara menghitung luasnya. Virtual function yang kita bangun pada dua chapter sebelumnya adalah mesin polymorphism runtime di C++. Chapter ini menghidupkannya dalam skenario yang paling sering dipakai di dunia nyata: satu container berisi pointer ke class dasar, berisi campuran object yang berbeda-beda, dan satu loop yang memperlakukan semuanya sama.
Satu Antarmuka, Banyak Perilaku #
Tanpa polymorphism, koleksi campuran diproses dengan rantai if atau switch yang memeriksa jenis object satu per satu — dan setiap jenis baru memaksa kita menyunting semua percabangan itu. Dengan polymorphism, logika “jenis apa ini?” dipindahkan ke dalam object sendiri: setiap class turunan membawa versi virtual function-nya, dan dynamic dispatch memilih versi yang tepat tanpa satu pun percabangan dari sisi pemanggil. Kode pemanggil ditulis sekali, menghadapi satu antarmuka, dan tetap benar saat jenis-jenis baru ditambahkan di kemudian hari.
Syarat yang harus dipenuhi sudah kita kenal semua: hierarki class dengan virtual function di dasarnya, dan pemanggilan lewat pointer atau referensi dasar. Yang baru pada chapter ini adalah skala pemakaiannya — bukan satu pointer yang menunjuk satu object, melainkan container yang menampung banyak pointer ke object yang jenisnya beragam.
Container Pointer Dasar #
std::vector<Bentuk*> adalah pola khasnya: satu container, dan setiap slotnya boleh menunjuk object jenis apa pun yang mewarisi Bentuk. Ketika loop memanggil b->luas() untuk setiap elemen, program melihat jenis object yang ditunjuk — bukan tipe Bentuk* yang tertulis — dan menjalankan versi luas() yang sesuai. Berikut program lengkapnya dengan tiga jenis bentuk dalam satu koleksi:
#include <iomanip>
#include <iostream>
#include <string>
#include <vector>
class Bentuk {
public:
virtual double luas() const {
return 0.0;
}
virtual std::string nama() const {
return "Bentuk";
}
};
class Lingkaran : public Bentuk {
private:
double jari;
public:
explicit Lingkaran(double r) : jari(r) {}
double luas() const override {
return 3.141592653589793 * jari * jari;
}
std::string nama() const override {
return "Lingkaran";
}
};
class Persegi : public Bentuk {
private:
double sisi;
public:
explicit Persegi(double s) : sisi(s) {}
double luas() const override {
return sisi * sisi;
}
std::string nama() const override {
return "Persegi";
}
};
class Segitiga : public Bentuk {
private:
double alas;
double tinggi;
public:
Segitiga(double a, double t) : alas(a), tinggi(t) {}
double luas() const override {
return 0.5 * alas * tinggi;
}
std::string nama() const override {
return "Segitiga";
}
};
int main() {
Lingkaran lingkaran(7.0);
Persegi persegi(5.0);
Segitiga segitiga(6.0, 4.0);
// Satu container, tiga jenis: setiap slot menyimpan pointer ke dasar
std::vector<Bentuk*> koleksi = { &lingkaran, &persegi, &segitiga };
double total = 0.0;
for (const Bentuk* b : koleksi) {
double l = b->luas(); // versi diambil saat runtime, per object
total += l;
std::cout << std::fixed << std::setprecision(2)
<< b->nama() << " -> luas " << l << "\n";
}
std::cout << "Total luas: " << total << "\n";
return 0;
}Output yang Dihasilkan:
Lingkaran -> luas 153.94
Persegi -> luas 25.00
Segitiga -> luas 12.00
Total luas: 190.94Mari kita bedah:
- Baris 8-13 (virtual function di
Bentuk): class dasar menyediakan antarmukaluas()dannama(). Versi dasarnya masih bisa dijalankan (return0.0); pada Chapter Abstract Classes versi ini kita paksa tanpa isi agar tiap bentuk wajib mengisinya. - Baris 21, 34, 48 (override di tiga class): setiap bentuk menghitung luas dengan rumusnya sendiri. Ketiganya cocok signature dengan dasar, dan
overridemenjaminnya. - Baris 57-59 dan 62 (container pointer): ketiga object hidup di stack
main(), lalu koleksi hanya menyimpan alamatnya. Ini penting: pointer membiarkan object asli tetap utuh — persis kebalikan dari menyalin by value yang akan kita lihat merusak polimorfisme pada Chapter Object Slicing. - Baris 65-70 (satu loop untuk semua):
b->luas()menjawab tiga jawaban berbeda pada tiga iterasi — 153.94, 25.00, 12.00. Tidak ada satu punifyang memeriksa jenis; vtable yang bekerja per object. Formatstd::fixeddanstd::setprecision(2)dari header<iomanip>menjaga tampilan dua desimal. - Baris 71 (total): akumulasi memperlakukan ketiga bentuk identik. Bila besok
Trapesiumditambahkan, loop ini tidak disentuh sama sekali — hanya class baru dengan dua override yang ditulis.
Runtime vs Compile-Time #
Polymorphism di C++ punya dua wajah, dan keduanya kita sudah lewati. Polymorphism runtime yang baru saja dibangun bekerja saat program berjalan: keputusan diambil per object lewat vtable, sehingga jenis object boleh ditentukan sejak program berjalan (dari input pengguna, dari file, dari jaringan). Polymorphism compile-time bekerja saat kompilasi: function overloading dan template memilih versi berdasarkan tipe yang diketahui compiler — cetak(int) dan cetak(double) adalah dua fungsi berbeda yang dipilih tanpa biaya runtime.
Pembeda utamanya adalah kapan informasinya tersedia. Bila jenis object baru diketahui ketika program sudah berjalan — dan jenisnya beragam dalam satu koleksi — runtime polymorphism lewat virtual function adalah jalurnya. Bila semua tipe sudah jelas pada saat kompilasi dan performa ekstrem menjadi syarat, template (Section Generic Programming dan Templates) mengambil alih dengan cara yang berbeda sama sekali.
Kapan Polymorphism Menjadi Pilihan #
Gunakan polymorphism runtime ketika kode harus menangani “semua yang bisa X” tanpa peduli bagaimana masing-masing melakukannya: semua bentuk bisa dihitung luasnya, semua dokumen bisa dicetak, seluruh karakter yang belum diperbarui pada game diolah tiap frame. Tanda-tandanya: daftar jenis beragam yang berkembang, satu operasi yang berarti bagi semua, dan keinginan menambah jenis tanpa mengedit kode lama.
Kebalikannya juga perlu dipegang: bila jenisnya tetap dua-tiga dan tidak akan tumbuh, switch biasa sering lebih sederhana; bila yang dibutuhkan hanya menghemat rumus yang beda parameter, overload function sudah cukup; dan bila perilaku harus bisa berganti di tengah jalan untuk object yang sama, strategy object yang disimpan sebagai member (komposisi!) kadang lebih lentur. Polymorphism adalah alat untuk variasi antar jenis, bukan konfigurasi.
Dengan pola koleksi pointer dasar ini di tangan, ada satu rasa tidak nyaman yang tersisa dari contoh: luas() versi Bentuk mengembalikan 0.0 — jawaban yang tidak seharusnya ada. Chapter berikutnya, abstract classes, menuntaskannya dengan melarang Bentuk dibuat sama sekali.
Rujukan resmi: Virtual specifier (cppreference) , Derived classes (cppreference) .