Pada Chapter Polymorphism, class Bentuk kita masih bisa dibuat object-nya — dan bila dibuat, luas()-nya menjawab 0.0, jawaban yang tidak bermakna untuk “bentuk” yang tidak diketahui wujudnya. Class dasar yang seperti ini adalah kerentanan desain: compiler tidak menghalangi pemanggil membuat object yang tidak seharusnya ada. Abstract class menutup celah itu dengan satu perubahan kecil: menandai virtual function sebagai pure virtual. Sejak saat itu, class dasar tidak bisa diinstansiasi sama sekali — ia hanya bisa diwarisi, dan turunannya wajib mengisi kontrak yang tertulis di sana. Chapter ini membahas cara kerjanya dan pola antarmuka yang dibukanya.

Dari Virtual ke Pure Virtual #

Pure virtual function adalah virtual function yang dinyatakan tanpa isi, ditandai = 0 pada akhir deklarasinya:

cpp
virtual double luas() const = 0;   // pure virtual: tidak ada implementasi di sini

Aturannya sederhana dan tegas: class yang memiliki minimal satu pure virtual function menjadi abstract class. Abstract class tidak bisa diinstansiasi — Bentuk b; langsung ditolak compiler — tetapi pointer dan referensi ke sana tetap sah, sekaligus menjadi pintu polymorphism yang sudah kita kenal. Bila class turunan mengisi semua pure virtual function, ia menjadi class biasa yang bisa dibuat; bila ada yang belum diisi, turunan itu ikut menjadi abstract.

Bacaan = 0 yang paling dekat dengan maksudnya: “nilainya nol definisi di sini — definisinya tanggung jawab turunan”. Ini bukan penugasan nilai, bukan pula nullptr; ia hanya sintaks khusus yang menandai istilah pure virtual.

Kontrak Antarmuka #

Abstract class yang baik bekerja seperti lembar spesifikasi. Ia menyatakan semua class keluarga ini pasti bisa melakukan apa — luas(), nama() — tanpa menyatakan bagaimana. Siapa pun yang menulis class turunan baru dipaksa membaca kontrak itu: bila salah satu kontrak belum diisi, compiler menolak object-nya dibuat. Kesalahan “lupa mengimplementasi” yang biasanya bersembunyi sampai runtime kini berhenti di waktu kompilasi.

Yang sering mengejutkan pemula: abstract class tetap boleh punya data member dan method konkret. Ia bukan “class kosong”, melainkan class yang sengaja tidak bisa diinstansiasi. Method konkret di dalamnya justru sering menjadi kekuatan utama pola ini: satu method yang mengatur alur, memanggil kontrak-kontrak yang akan diisi turunan. Pada contoh di bawah, tampil() menulis pola kerjanya — cetak nama, cetak luas — tanpa tahu rumus siapa yang akan dijalankan.

Contoh Lengkap: Kontrak Bentuk #

Program berikut menjadikan Bentuk abstract, memaksa dua turunannya mengisi kontrak, lalu memperlakukan keduanya seragam lewat pointer ke class abstract:

cpp
#include <iomanip>
#include <iostream>
#include <string>
#include <vector>

// Abstract class: punya minimal satu pure virtual function
class Bentuk {
public:
    virtual double luas() const = 0;      // kontrak: wajib diisi turunan
    virtual std::string nama() const = 0;

    // Method konkret tetap sah di abstract class
    void tampil() const {
        std::cout << nama() << " memiliki luas " << luas() << "\n";
    }
};

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 PersegiPanjang : public Bentuk {
private:
    double panjang;
    double lebar;
public:
    PersegiPanjang(double p, double l) : panjang(p), lebar(l) {}
    double luas() const override {
        return panjang * lebar;
    }
    std::string nama() const override {
        return "Persegi Panjang";
    }
};

int main() {
    Lingkaran lingkaran(10.0);
    PersegiPanjang persegi(8.0, 3.0);

    // Pointer ke abstract class: sah dan justru inilah polanya
    std::vector<Bentuk*> koleksi = { &lingkaran, &persegi };

    std::cout << std::fixed << std::setprecision(2);
    for (const Bentuk* b : koleksi) {
        b->tampil();
    }
    return 0;
}

Output yang Dihasilkan:

text
Lingkaran memiliki luas 314.16
Persegi Panjang memiliki luas 24.00

Mari kita bedah:

  1. Baris 9-10 (= 0): dua pure virtual function menjadikan Bentuk abstract. Tidak ada lagi versi dasar yang mengembalikan 0.0 — pertanyaan “luas bentuk tak dikenal?” kini tidak bisa ditanyakan sama sekali.
  2. Baris 13-15 (tampil()): method konkret di dalam class abstract. Ia memanggil nama() dan luas() yang kontraknya belum diisi — sah, karena ia hanya akan dijalankan pada object turunan yang pasti sudah mengisi keduanya.
  3. Baris 23-28 (override di Lingkaran): turunan mengisi kontrak. Penanda override memeriksa kesesuaian signature; tanpa kedua override ini, Lingkaran ikut menjadi abstract dan baris 46 akan gagal dikompilasi.
  4. Baris 50 (pointer ke abstract): vector<Bentuk*> sah — yang dilarang adalah membuat object Bentuk, bukan menunjuk ke object turunannya. Dynamic dispatch bekerja sama seperti sebelumnya.
  5. Baris 53-54 (satu loop): b->tampil() menjalankan method konkret yang polanya satu, dengan kontrak yang jawabannya beda per object — output membuktikannya: 314.16 untuk lingkaran, 24.00 untuk persegi panjang.

Bila kita melanggar kontraknya, compiler menegur dengan pesan yang sangat spesifik. Membuat Bentuk b; langsung ditolak:

text Error kompilasi: abstract class tidak bisa diinstansiasi
error: cannot declare variable 'b' to be of abstract type 'Bentuk'
note:   because the following virtual functions are pure within 'Bentuk':
    'virtual double Bentuk::luas() const'

Dan turunan yang tidak mengisi semua kontrak ikut menjadi abstract — Segitiga : public Bentuk { }; tanpa override pun ditolak dengan cara yang sama:

text Error kompilasi: turunan yang belum mengisi kontrak
error: cannot declare variable 's' to be of abstract type 'Segitiga'
note:   because the following virtual functions are pure within 'Segitiga':
    'virtual double Bentuk::luas() const'

Pointer Abstract Class dan Desain yang Terbuka #

Pola “pointer ke abstract class” adalah fondasi dari banyak desain C++ yang terkenal: pabrik yang mengembalikan Produk* tanpa membocorkan jenis produknya, sistem plugin yang menerima Modul* dari pihak ketiga, antrean tugas yang berisi Tugas* dari mana pun. Class abstract menyembunyikan semua jenis konkret di balik satu kontrak, sehingga kode pemakai tidak pernah perlu berubah saat keluarga turunannya bertambah.

Bandingkan dengan class dasar “biasa” pada Chapter Polymorphism: versi default yang bisa dijalankan memberi ruang bagi object yang tidak mengisi apa pun. Abstract class menutup ruang itu — pilihan desain yang tepat ketika tidak ada perilaku default yang bermakna. “Bentuk tanpa wujud” memang tidak seharusnya ada.

Satu detail menarik tersisa dari pure virtual: C++ tetap mengizinkan kita menulis implementasi untuknya — ya, pure virtual dengan isi. Chapter berikutnya membahasnya: fungsi yang wajib di-override namun tetap punya jawaban standar yang bisa dipinjam turunannya.

Rujukan resmi: Abstract classes (cppreference) , Virtual specifier (cppreference) .