Dasar-dasar overloading

Member function versus free function dan operator yang simetris.

Daftar isi

Topik ini membahas dasar-dasar overloading operator pada C++, yaitu mekanisme yang memberi makna operator bawaan seperti + dan - kepada tipe kelas buatan sendiri. Pembahasan mencakup dua bentuk penulisannya, member function dan free function, beserta alasan operator biner simetris sebaiknya ditulis sebagai free function. Setelah membaca topik ini, operator pada tipe buatan sendiri dapat ditulis dalam dua bentuk yang tersedia, lengkap dengan alasan pemilihannya.

Aturan dasar overloading operator#

Overloading operator ditulis sebagai fungsi yang namanya diawali kata kunci operator diikuti simbol operatornya. Fungsi operator+ menangani +, fungsi operator< menangani <, dan seterusnya. Compiler memanggil fungsi tersebut secara otomatis setiap kali ekspresi memakai simbolnya pada tipe kelas, sehingga bentuk ekspresinya tetap sama seperti ekspresi aritmetika biasa.

Beberapa batasan perlu dipahami sejak awal:

  • Operator baru tidak dapat diciptakan, misalnya simbol ** untuk pemangkatan.
  • Jumlah operand dan prioritas operator (precedence) tetap mengikuti operator bawaannya, sehingga perkalian tetap dievaluasi lebih dahulu dibandingkan penjumlahan.
  • Operator ::, ., .*, dan ?: sama sekali tidak dapat dioverload.
  • Minimal satu operand harus berupa tipe kelas atau enum, sehingga perilaku 1 + 2 pada tipe fundamental tidak dapat diubah.

Selain batasan sintaks, ada batasan kesepakatan. Pembaca kode mengharapkan + menghasilkan nilai gabungan tanpa mengubah kedua operandnya, dan == menguji kesamaan tanpa efek samping. Operator yang maknanya menyimpang dari kebiasaan tersebut membuat program sulit dibaca. Karena itu, makna yang diberikan kepada sebuah operator sebaiknya mengikuti perilaku alami operator aslinya.

Overloading sebagai member function#

Bentuk pertama adalah member function, yaitu fungsi yang dideklarasikan di dalam kelas. Pada operator biner, operand kiri berperan sebagai objek *this itu sendiri, sehingga parameter eksplisitnya hanya satu, yaitu operand kanan. Operator unari seperti unary minus tidak memerlukan parameter sama sekali. Bentuk ini cocok untuk operator yang secara alami mengubah objek kirinya, misalnya +=.

Program berikut mendefinisikan kelas Vektor2D dengan operator+ dan unary minus sebagai member function. Jalankan programnya dan perhatikan bahwa ekspresi penjumlahan maupun negasi ditulis persis seperti ekspresi biasa.

cpp vektor-member.cpp
#include <iostream>

class Vektor2D {
public:
    Vektor2D(double x, double y) : x(x), y(y) {}

    // operator + sebagai member function: operand kiri adalah *this
    Vektor2D operator+(const Vektor2D& lain) const {
        return Vektor2D(x + lain.x, y + lain.y);
    }

    // unary minus sebagai member function
    Vektor2D operator-() const {
        return Vektor2D(-x, -y);
    }

    double getX() const { return x; }
    double getY() const { return y; }

private:
    double x;
    double y;
};

int main() {
    Vektor2D a(3.0, 4.0);
    Vektor2D b(1.0, 2.0);

    Vektor2D c = a + b;   // a.operator+(b)
    Vektor2D d = -a;      // a.operator-()

    std::cout << "c = (" << c.getX() << ", " << c.getY() << ")\n";
    std::cout << "d = (" << d.getX() << ", " << d.getY() << ")\n";
    return 0;
}
text Keluaran
c = (4, 6)
d = (-3, -4)

Ekspresi a + b diterjemahkan compiler menjadi pemanggilan a.operator+(b), sedangkan -a menjadi a.operator-(). Karena operand kiri selalu objek kelasnya, member function tidak memberi kesempatan kepada compiler untuk mengonversi operand kiri dari tipe lain. Keterbatasan ini tidak terasa pada contoh Vektor2D yang kedua operandnya selalu bertipe sama, tetapi menjadi masalah pada operator yang digabungkan dengan tipe lain, misalnya pecahan yang dikalikan bilangan bulat.

Overloading sebagai free function#

Bentuk kedua adalah free function, yaitu fungsi biasa yang dideklarasikan di luar kelas dan menerima kedua operand sebagai parameter. Compiler mempertimbangkan konversi implisit pada kedua parameter, sehingga 2 * a dapat dievaluasi. Nilai 2 berubah menjadi objek kelas melalui constructor konversi, yaitu constructor berparameter tunggal yang menerima tipe lain. Sifat inilah yang membuat free function disebut simetris: kedua operand diperlakukan setara.

Program berikut memakai kelas Pecahan dengan constructor konversi dari int, lalu mendefinisikan operator* sebagai free function. Amati bahwa perkalian berhasil dalam tiga bentuk penulisan operandnya.

cpp pecahan-simetri.cpp
#include <iostream>

class Pecahan {
public:
    Pecahan(int pembilang, int penyebut) : pembilang(pembilang), penyebut(penyebut) {}

    // constructor konversi: int dapat berubah menjadi Pecahan secara implisit
    Pecahan(int pembilang) : pembilang(pembilang), penyebut(1) {}

    int getPembilang() const { return pembilang; }
    int getPenyebut() const { return penyebut; }

private:
    int pembilang;
    int penyebut;
};

// free function: kedua operand dapat menerima konversi tipe
Pecahan operator*(const Pecahan& a, const Pecahan& b) {
    return Pecahan(a.getPembilang() * b.getPembilang(),
                   a.getPenyebut() * b.getPenyebut());
}

int main() {
    Pecahan a(2, 3);
    Pecahan b(3, 5);

    Pecahan c = a * b;   // Pecahan * Pecahan
    Pecahan d = a * 2;   // 2 dikonversi menjadi Pecahan(2)
    Pecahan e = 2 * a;   // hanya bekerja dengan free function

    std::cout << "a * b = " << c.getPembilang() << "/" << c.getPenyebut() << "\n";
    std::cout << "a * 2 = " << d.getPembilang() << "/" << d.getPenyebut() << "\n";
    std::cout << "2 * a = " << e.getPembilang() << "/" << e.getPenyebut() << "\n";
    return 0;
}
text Keluaran
a * b = 6/15
a * 2 = 4/3
2 * a = 4/3

Pada keluaran tersebut, a * 2 maupun 2 * a menghasilkan pecahan yang sama, yaitu 4/3. Apabila operator* ditulis sebagai member function, baris 2 * a gagal dikompilasi karena 2 bertipe int dan tidak memiliki member operator*. Pesan errornya berupa tidak adanya kandidat operator yang cocok, dan penyebabnya tidak selalu jelas bagi pembaca kode.

Pemilihan bentuk dan akses member private#

Free function tidak otomatis memiliki akses ke member private. Apabila implementasi operator memerlukannya, pilihannya ada dua, yaitu menggunakan accessor publik seperti getPembilang() atau mendeklarasikan fungsi tersebut sebagai friend di dalam kelas. Contoh di atas memakai accessor agar kelasnya tetap tertutup, sedangkan friend biasanya dipakai ketika operator memang bagian dari antarmuka kelas, misalnya operator stream.

Kebiasaan yang dipakai pustaka standar dan dokumentasi C++ dirangkum pada tabel berikut.

KebutuhanBentuk yang disarankan
Operator yang mengubah objek kiri, misalnya += dan =member function
Operator simetris yang menghasilkan nilai baru, misalnya +, *, dan ==free function
Operator unari, misalnya unary minusmember function
Operator << dan >>free function yang dideklarasikan friend

Tabel tersebut bukan aturan bahasa, melainkan konvensi. += ditulis sebagai member karena operasinya melekat pada objek kiri, sedangkan + ditulis sebagai free function karena hasilnya objek baru dan kedua operand setara. Operator unari seperti unary minus dan ! cukup ditulis sebagai member karena hanya melibatkan satu objek, sehingga persoalan konversi operand kiri tidak muncul. Banyak program menulis + dengan memanfaatkan += agar kedua operator selalu konsisten satu sama lain. Konvensi yang diikuti secara konsisten juga membuat bentuk definisi setiap operator dapat ditebak tanpa membuka implementasinya.

Kesalahan umum#

  • Operator simetris ditulis sebagai member function. Gejalanya berupa error kompilasi pada ekspresi yang operand kirinya bertipe lain. Perbaikannya dengan memindahkan operator menjadi free function.
  • Mengubah semantik operator, misalnya operator+ yang memodifikasi operand kiri. Ekspresi a + b diharapkan tidak mengubah a, sehingga program yang membaca a setelahnya berperilaku tak terduga. Perbaikannya dengan mengembalikan objek baru.
  • Mengembalikan referensi ke objek lokal, misalnya tipe balik const Vektor2D& yang menunjuk variabel lokal di dalam operator+. Objek lokal dihancurkan saat fungsi berakhir, sehingga referensinya menggantung dan programnya mengalami undefined behavior, yaitu perilaku yang tidak terdefinisi oleh standar. Perbaikannya dengan mengembalikan nilai biasa, karena compiler dapat menyederhanakan penyalinan hasilnya.

Ringkasan#

  • Overloading operator ditulis sebagai fungsi bernama operator diikuti simbolnya, dengan batasan berupa operator bawaan saja, jumlah operand dan prioritas yang tetap, serta minimal satu operand bertipe kelas.
  • Member function menjadikan operand kiri sebagai *this, sehingga operand kiri tidak menerima konversi tipe.
  • Free function menerima kedua operand sebagai parameter dan setara terhadap konversi tipe, sehingga menjadi pilihan utama untuk operator biner simetris seperti + dan ==.

Referensi#