Membuat Tipemu Terasa Seperti Tipe Bawaan
Kamu sudah tahu bahwa std::string membuatmu bisa menulis a + b untuk menyambung dan cout << s untuk mencetak. Itu bukan trik khusus compiler; keduanya adalah fungsi biasa dengan nama yang unik. Operator overloading adalah fitur yang membuat class milikmu bisa memakai sintaks yang sama, sehingga tipe Vector2 atau Money bisa dijumlahkan, dibandingkan, dan dicetak persis seperti int.
Mekanismenya sederhana begitu kamu melihatnya: ekspresi seperti a + b hanyalah singkatan. Compiler menulis ulang ekspresi itu menjadi pemanggilan fungsi bernama operator+ dan mencari fungsi yang cocok dengan tipe operannya. Definisikan fungsi itu untuk class-mu, dan a + b tiba-tiba berfungsi. Ini sebenarnya bentuk khusus dari function overloading: aturan pencarian nama yang sama berlaku, hanya saja dengan nama berbentuk operator.
Perhatikan bahwa fungsi itu menerima kedua operan lewat const&: aritmetika tidak seharusnya mengubah input-nya, dan referensi menghindari penyalinan. Fungsi itu mengembalikan Vector2 yang baru by value: p + q harus menghasilkan hasil baru tanpa menyentuh p atau q, sama seperti 2 + 3 tidak mengubah 2.
Member vs Non-Member
Ada dua tempat untuk mendefinisikan operator: sebagai member class, atau sebagai fungsi bebas (non-member). Sebagai member, operan kirinya adalah this yang implisit, jadi operator biner hanya menerima satu parameter eksplisit:
const setelah daftar parameter itu penting: a + b tidak seharusnya mengubah a, jadi member-nya ditandai const. Pakai bentuk member untuk operator yang pada dasarnya terikat pada operan kiri dan tidak butuh konversi padanya: +=, [], (), ->, dan operator unary seperti -x atau ++x.
Masalah dengan member: operan kirinya tidak bisa dikonversi. Dengan operator+ member di atas, a + 50 berfungsi (50 dikonversi menjadi Money untuk sisi kanan), tetapi 50 + a tidak bisa dikompilasi: operan kiri 50 adalah int, dan kamu tidak bisa menambahkan member function ke int. Operator non-member memperbaikinya karena kedua operan adalah parameter eksplisit dan keduanya bisa dikonversi:
Patokannya: jadikan operator biner yang simetris (+, ==, *) sebagai non-member supaya konversi berfungsi di kedua sisi; jadikan operator yang harus mengubah atau terikat pada operan kiri (+=, [], =) sebagai member.
Overloading Operator Stream
Operator yang paling sering di-overload adalah << untuk mencetak. Kamu tidak bisa menjadikannya member class-mu, karena operan kirinya adalah std::ostream (seperti cout), bukan tipemu, dan kamu bukan pemilik ostream. Jadi operator ini selalu non-member yang menerima stream lewat referensi non-const dan mengembalikannya:
Ada dua detail yang membuatnya berfungsi. Stream diteruskan dan dikembalikan by reference (ostream&): stream tidak bisa disalin, dan mengembalikan stream yang sama itulah yang membuatmu bisa merangkai cout << "p = " << p << "\n". Setiap << mengembalikan stream sehingga << berikutnya punya sesuatu untuk diikat. Lupa menulis return os; dan rangkaiannya rusak.
Operator Perbandingan
Untuk membandingkan objekmu dengan ==, <, dan kawan-kawannya, overload operator perbandingan. Sebelum C++20 kamu menulis masing-masing secara manual; jebakan utamanya adalah operator< harus mengembalikan bool dan mendefinisikan urutan yang konsisten:
Menulis keenam perbandingan (==, !=, <, <=, >, >=) secara manual itu membosankan dan rawan salah. C++20 menambahkan operator perbandingan tiga arah <=> (si "spaceship"). Menjadikannya default bersama == membuat semua perbandingan dibuatkan untukmu:
= default menyuruh compiler membandingkan member sesuai urutan deklarasinya, yang persis merupakan urutan leksikografis yang akan kamu tulis secara manual. Utamakan cara ini di compiler modern.
Operator Penugasan dan Jebakannya
operator= (copy assignment) itu istimewa: compiler membuatkannya untukmu, dan untuk class sederhana versi default itu sudah benar. Kamu hanya perlu menulis versimu sendiri saat class-mu mengelola sebuah resource, seperti memori mentah atau file handle, ketika menyalin member satu per satu akan salah. Signature kanonisnya mengembalikan *this by reference supaya penugasan bisa dirangkai (a = b = c):
Ada dua jebakan di fungsi pendek ini. Pertama, pengecekan self-assignment if (this == &other): tanpa itu, a = a akan melakukan delete[] data lalu membaca dari other.data yang baru saja dibebaskan, yaitu undefined behavior. Kedua, urutannya penting: di versi buatan sendiri kamu tidak boleh menghapus buffer lama sebelum berhasil menyalin yang baru dengan aman (implementasi sungguhan sering mengalokasikan dulu, atau memakai idiom copy-and-swap, supaya alokasi yang gagal tidak merusak objeknya).
Jebakan yang lebih luas: jangan meng-overload operator dengan cara yang mengejutkan. operator+ yang diam-diam mengubah operan kirinya, atau operator== yang tidak simetris, akan membingungkan setiap pembaca dan merusak kode standard library yang mengandalkan arti yang biasa. Overload operator hanya saat operasinya memang "seperti penjumlahan" atau "seperti kesamaan" untuk tipemu.
Berikutnya: Access Specifier
Perhatikan bagaimana setiap contoh menjaga data member-nya tetap private dan membuka perilakunya lewat antarmuka public yang kecil: constructor, operator, dan beberapa method. Batas antara apa yang terlihat oleh dunia luar dan apa yang tersembunyi di dalam class diatur oleh access specifier: public, private, dan protected. Berikutnya kita akan melihat persis apa yang diizinkan masing-masing, kenapa data private dengan method public adalah default untuk enkapsulasi yang baik, dan bagaimana protected berperan dalam pewarisan.
Pertanyaan yang sering diajukan
Apa itu operator overloading di C++?
Operator overloading membuatmu bisa menentukan arti operator bawaan seperti +, ==, atau << untuk tipe buatanmu sendiri. Kamu menulis fungsi dengan nama khusus, seperti operator+, operator==, dan seterusnya, dan compiler memanggilnya setiap kali operator itu muncul dengan operan bertipe class-mu. Begitulah string + string menyambung string dan cout << obj mencetak objek buatan sendiri.
Sebaiknya operator dijadikan member function atau non-member (friend) function di C++?
Pakai member function saat operan kiri adalah class-mu sendiri dan tidak butuh konversi (misalnya +=, [], ()). Pakai fungsi non-member (sering kali friend) saat operan kiri bisa berupa tipe bawaan atau saat kamu menginginkan konversi yang simetris di kedua sisi. Ini wajib untuk operator<< karena operan kirinya adalah std::ostream, bukan class-mu.
Operator C++ mana saja yang tidak bisa di-overload?
Kamu tidak bisa meng-overload :: (scope resolution), . (akses member), .* (akses member pointer), ?: (ternary), dan sizeof. Kamu juga tidak bisa menciptakan operator yang benar-benar baru atau mengubah jumlah operan maupun prioritas sebuah operator: + selalu biner dengan prioritas yang sama, baik saat menjumlahkan int maupun Vector2 milikmu.