Setiap kali kita memanggil mobilA.tambahKecepatan(20), ada pertanyaan yang belum kita jawab: bagaimana member function tahu bahwa data yang harus diubah adalah milik mobilA, bukan mobilB? Jawabannya ada di pointer tersembunyi bernama this — parameter implisit yang otomatis melekat pada setiap pemanggilan member function dan menunjuk ke object pemanggil. Chapter ini membongkar mekanisme tersebut: cara compiler menggunakannya tanpa kita sadari, cara kita memakainya secara sadar, dan trik return *this yang memungkinkan method chaining.
Apa itu this #
Di dalam badan member function non-static, this adalah pointer bertipe NamaClass* yang menyimpan alamat object yang memanggil fungsi tersebut. Ketika kita menulis mobilA.cetakStatus(), compiler sesungguhnya meneruskan &mobilA sebagai argumen tersembunyi, dan di dalam cetakStatus() setiap penyebutan merek atau kecepatan dibaca sebagai this->merek dan this->kecepatan. Inilah jawaban teka-teki di atas: fungsi yang sama dieksekusi untuk banyak object, tetapi this di setiap eksekusi menunjuk object yang berbeda.
Konsekuensi praktisnya ada dua. Pertama, kita tidak pernah perlu menulis this-> untuk akses member biasa — compiler menambahkannya sendiri. Kedua, this tersedia di dalam member function apa pun bila kita memang membutuhkan alamat object tersebut, misalnya untuk membandingkan dua object (“apakah ini object yang sama?”) atau untuk mengembalikan dirinya sendiri. Satu pengecualian yang akan kita bahas nanti: member function static tidak punya this, karena ia tidak terikat pada object mana pun.
Membedakan Member dan Parameter yang Bernama Sama #
Kasus penggunaan this yang paling sering: nama parameter constructor atau setter sama persis dengan nama data member-nya. Penulisan lebar = lebar; di dalam setLebar ambigu — keduanya merujuk parameter, dan data member tidak tersentuh (beberapa compiler memberi peringatan untuk penugasan ke diri sendiri). Dengan this->lebar = lebar;, maknanya tegas: member lebar diisi dari parameter lebar.
Kotak& setLebar(int lebar) {
this->lebar = lebar; // this->lebar = data member; lebar = parameter
return *this;
}Pola ini lazim di kode nyata, dan bersih bila dibaca dengan disiplin: kiri dari tanda sama dengan selalu diawali this->. Alternatif yang juga banyak dipakai adalah memberi prefiks berbeda, misalnya parameter bernama lebarBaru atau member diawali m_lebar — pilih satu konvensi dan konsisten di seluruh proyek. Yang tidak boleh adalah diam-diam menulis lebar = lebar; lalu bertanya-tanya kenapa object tidak berubah.
return *this untuk Method Chaining #
this adalah pointer, sehingga *this adalah object itu sendiri — hasil dereferensi pointer. Bila sebuah member function mengembalikan *this dengan tipe kembalian referensi Kotak&, fungsi tersebut mengembalikan object pemanggil, dan pemanggil bisa langsung melanjutkan dengan member function berikutnya: k.setLebar(4).setTinggi(3).cetak();. Pola ini disebut method chaining (atau fluent interface) dan kita sudah pernah melihatnya dari sisi lain — di std::cout << a << b, operator << mengembalikan stream-nya sendiri untuk dirangkai.
Chain seperti ini bekerja karena setiap tautan mengembalikan object yang sama. k.setLebar(4) mengubah k lalu mengembalikan referensi ke k; .setTinggi(3) dieksekusi terhadap k itu lagi, dan seterusnya. Perlu diperhatikan: tipe kembalian Kotak& (referensi) menjaga seluruh rantai mengubah satu object yang sama; bila kita keliru mengembalikan Kotak (nilai), setiap tautan akan bekerja pada salinan sementara dan perubahan tidak menempel.
Contoh Lengkap: Chaining dan Pointer ke Object #
Program berikut memakai this untuk dua tujuan sekaligus — membedakan member dari parameter, dan mengaktifkan chaining — lalu menutup dengan akses lewat pointer untuk mengingatkan relasi -> dan this:
#include <iostream>
class Kotak {
private:
int lebar;
int tinggi;
public:
// Parameter bernama sama dengan data member: this membedakannya
Kotak& setLebar(int lebar) {
this->lebar = lebar; // this->lebar = data member; lebar = parameter
return *this; // kembalikan object pemanggil untuk chaining
}
Kotak& setTinggi(int tinggi) {
this->tinggi = tinggi;
return *this;
}
int luas() const {
return lebar * tinggi;
}
void cetak() {
std::cout << "Kotak " << lebar << " x " << tinggi
<< ", luas = " << luas() << "\n";
}
};
int main() {
Kotak k;
k.setLebar(4).setTinggi(3).cetak(); // method chaining
Kotak* ptr = &k;
std::cout << "luas lewat pointer: " << ptr->luas() << "\n";
return 0;
}Output yang Dihasilkan:
Kotak 4 x 3, luas = 12
luas lewat pointer: 12Bedah baris demi baris:
- Baris 10-14 (
Kotak& setLebar(int lebar)): parameterlebarmenutupi data member yang bernama sama (shadowing), sehinggathis->lebarwajib untuk menyentuh member. Tipe kembalianKotak&bersamareturn *thismenjadikan fungsi ini mata rantai yang bisa disambung. - Baris 22-25 (
int luas() const): method hanya-baca; perhatikan kataconstdi akhirnya — pembehasan lengkapnya menjadi urusan Chapter berikutnya. - Baris 33 (
k.setLebar(4).setTinggi(3).cetak();): satu pernyataan, tiga pemanggilan, satu object.setLebar(4)mengubahkdan mengembalikank;setTinggi(3)meneruskan;cetak()menampilkan hasilnya. - Baris 35-37 (
Kotak* ptr = &k; ...): kita memegang alamatk— persis isi pointerthisketika method dipanggil atask— lalu mengakses member lewat->. Inilah mengapathissering disebut “pointer yang sama dengan yang Anda pegang, diberikan gratis di dalam method”.
Sekarang kita tahu siapa sebenarnya “pemilik” setiap akses member di dalam method: object yang ditunjuk this. Pertanyaan berikutnya wajar muncul: bagaimana bila object itu tidak boleh diubah sama sekali? Chapter Const Member Functions menjawabnya dengan kata kunci const di tempat yang mungkin belum pernah Anda lihat — setelah kurung parameter.
Rujukan resmi: this (cppreference) , Member functions (cppreference) .