Fungsi dapat menerima argumen melalui tiga mekanisme yang berbeda, yaitu pass by value, pass by reference, dan pass by pointer. Ketiganya menentukan apakah fungsi bekerja pada salinan nilainya atau pada variabel aslinya, sehingga pemilihannya memengaruhi hasil program dan kinerjanya. Topik ini membedah ketiga mekanisme tersebut dan menegaskan reference sebagai pilihan awal yang paling aman.
Pass by value: parameter menerima salinan#
Pada pass by value, nilai argumen disalin ke parameter fungsi, dan perubahan terhadap parameter tidak menyentuh variabel milik pemanggil. Inilah perilaku bawaan parameter C++. Program berikut menguji pass by value dan pass by reference sekaligus agar perbedaannya terlihat langsung.
#include <iostream>
void naikkanSalin(int nilai) {
nilai = nilai + 1;
}
void naikkanAsli(int& nilai) {
nilai = nilai + 1;
}
int main() {
int hitungan = 10;
naikkanSalin(hitungan);
std::cout << "Setelah pass by value: " << hitungan << '\n';
naikkanAsli(hitungan);
std::cout << "Setelah pass by reference: " << hitungan << '\n';
return 0;
}Setelah pass by value: 10
Setelah pass by reference: 11Pemanggilan naikkanSalin menyalin hitungan ke parameter nilai, lalu menaikkan salinan itu, sehingga hitungan tetap bernilai 10. Tanda & pada parameter int& nilai menjadikan parameter tersebut reference, yakni nama lain untuk variabel yang dikirim pemanggil. Akibatnya, naikkanAsli menaikkan hitungan yang asli dan nilainya berubah menjadi 11.
Pass by value tetap berguna untuk tipe kecil seperti int dan double, karena penyalinannya nyaris tanpa biaya dan fungsi dijamin tidak mengubah asalnya. Jebakannya muncul pada tipe besar, misalnya std::string berisi ribuan karakter, karena setiap pemanggilan menyalin seluruh isinya tanpa diminta.
Biaya penyalinan itu juga berkaitan dengan pustaka standar. Ketika fungsi menerima std::string dengan pass by value, salinan parameternya mengalokasikan memori baru dan menyalin karakter-karakternya satu per satu, pekerjaan yang tidak terjadi pada parameter const std::string&. Pada fungsi yang dipanggil berulang di dalam perulangan panjang, selisih biayanya terukur dan terasa.
Pass by pointer: alamat yang dikirim#
Pass by pointer mengirim alamat variabel, sehingga fungsi mencapai nilai aslinya melalui dereference. Mekanisme ini berasal dari bahasa C dan masih banyak dipakai pada kode warisan (legacy) serta antarmuka pustaka yang mengharuskan parameter dapat bernilai null.
#include <iostream>
void naikkanViaPointer(int* nilai) {
*nilai = *nilai + 1;
}
int main() {
int hitungan = 10;
naikkanViaPointer(&hitungan);
std::cout << "Setelah pass by pointer: " << hitungan << '\n';
return 0;
}Setelah pass by pointer: 11Operator & pada &hitungan mengambil alamat variabelnya, sedangkan operator * di dalam fungsi membaca dan menulis nilai pada alamat tersebut. Parameter int* nilai dibaca sebagai pointer ke int. Nama nilai menggambarkan isi yang diakses lewat alamat tersebut, bukan alamatnya sendiri. Dibandingkan reference, pointer menuntut langkah ekstra dan membuka kemungkinan alamat yang tidak menunjuk ke mana pun, sehingga pembahasan lengkapnya diserahkan ke modul pointer dan memori. Meskipun begitu, kemampuan pointer menyatakan ketiadaan objek menjadikannya tetap relevan pada antarmuka pustaka yang lebih tua.
Pemanggilan via pointer juga memunculkan sepasang operator yang bekerja bersama. Tanda & pada &hitungan berada di sisi pemanggil, sedangkan tanda * di dalam badan fungsi bekerja pada alamat yang dikirim, dan keduanya menunjuk variabel yang sama. Pasangan operator ini akan berulang pada modul pointer dan memori beserta aritmetikanya.
Reference sebagai pilihan default#
Reference menggabungkan kepraktisan sintaks pass by value dengan efek pass by pointer, tanpa perlu tanda * di dalam badan fungsi. Apabila fungsi hanya membaca argumen dan tidak mengubahnya, tambahkan const agar komitmen itu dinyatakan eksplisit. Bentuk const std::string& adalah konvensi umum untuk mengirim teks besar tanpa menyalinnya.
#include <iostream>
#include <string>
int hitungKata(const std::string& teks) {
if (teks.empty()) {
return 0;
}
int jumlah = 1;
for (char c : teks) {
if (c == ' ') {
++jumlah;
}
}
return jumlah;
}
int main() {
std::string kalimat = "kompilasi mengubah kode sumber menjadi program";
std::cout << "Jumlah kata: " << hitungKata(kalimat) << '\n';
return 0;
}Jumlah kata: 6Parameter const std::string& teks tidak menyalin kalimatnya, dan compiler menolak setiap upaya menulis ke teks di dalam fungsi. Kombinasi ini menjadikan const reference pilihan default untuk argumen yang besar, sedangkan pass by value dipertahankan untuk tipe kecil dan pass by pointer disisakan untuk kasus yang memang membutuhkan alamat.
Konvensi ini juga memudahkan fungsi yang menerima beragam bentuk masukan. Literal teks, variabel std::string, dan hasil fungsi lain sama-sama dapat dikirim ke parameter const std::string& tanpa penyesuaian di sisi pemanggil. Sifat luwes ini menjadikan bentuk tersebut tanda tangan (signature) yang paling sering muncul pada pustaka standar, yakni kombinasi nama fungsi beserta daftar parameternya. Apabila fungsi perlu mengubah teksnya, parameter std::string& tanpa const dipakai dan maksudnya terlihat jelas.
Perbandingan mekanisme pengiriman argumen#
Tabel berikut merangkum perbedaan keempat bentuk parameter pada tipe T yang sama.
| Mekanisme | Menyalin nilai | Dapat mengubah asal | Dapat menunjuk ketiadaan | Bentuk parameter |
|---|---|---|---|---|
| Pass by value | Ya | Tidak | Tidak | T |
| Pass by reference | Tidak | Ya | Tidak | T& |
| Pass by const reference | Tidak | Tidak | Tidak | const T& |
| Pass by pointer | Tidak | Ya, melalui dereference | Ya | T* |
Kolom kebolehan ketiadaan menjadi pembeda yang menentukan desain antarmuka. Parameter yang tidak boleh hilang dinyatakan reference, sehingga pemanggil tidak perlu memeriksa apa pun dan fungsi tidak perlu memeriksa null. Parameter yang memang boleh tidak ada dinyatakan pointer, dan kontraknya dinyatakan lewat nilai null yang dicek di dalam fungsi.
Pilihan yang konsisten membuat niat setiap tanda tangan fungsi terbaca dari deklarasinya saja. Bentuk T menyatakan penyalinan yang aman, const T& menyatakan pembacaan argumen besar, T& menyatakan perubahan yang terencana, dan T* menyatakan kebolehan ketiadaan objek. Tabel ini bukan aturan baku bahasa, melainkan titik awal yang sehat sebelum kebutuhan khusus muncul.
Kesalahan umum#
Lupa menambahkan & pada parameter yang semestinya reference membuat fungsi tampak berjalan lancar tetapi tidak menghasilkan apa pun, karena yang diubah hanyalah salinannya. Kesalahan ini sulit dikenali karena compiler tidak memberi error. Apabila fungsi seharusnya mengubah argumen pemanggil, periksa tanda & pada deklarasinya. Membaca tanda tangan fungsi di header sebelum menulis pemanggilan adalah pemeriksaan awal yang murah.
Mengembalikan reference ke variabel lokal juga menghasilkan program yang rusak. Variabel lokal lenyap saat fungsinya berakhir, sehingga reference yang dikembalikan menunjuk ke memori yang sudah tidak sah dan perilakunya tidak terdefinisi. Nilai kembalian seharusnya dikirim by value, atau referensinya ditujukan ke objek yang masa hidupnya (lifetime) lebih panjang daripada fungsi.
Pointer yang diterima fungsi dapat bernilai null, dan dereference terhadap null menyebabkan program berhenti mendadak. Fungsi yang menerima pointer wajib memeriksa keabsahannya lebih dahulu, sedangkan reference tidak perlu diperiksa karena sudah mewakili objek yang ada. Pertimbangan inilah yang menjadikan reference pilihan default. Kontrak tanpa null ini menghilangkan satu cabang pemeriksaan pada setiap pemanggilan.
Ringkasan#
- Pass by value menyalin argumen sehingga pemanggil terlindung dari perubahan, dengan biaya penyalinan yang terasa pada tipe besar.
- Pass by reference menyambungkan parameter ke variabel aslinya, dan bentuk
const T&menjadi konvensi default untuk membaca argumen besar tanpa menyalin. - Pass by pointer mengirim alamat dan menuntut pemeriksaan null, sehingga dipakai hanya bila kemungkinan ketiadaan objek memang diperlukan.
Referensi#
- References — cppreference.com — Kaidah deklarasi reference dan hubungannya dengan objek yang dirujuk.
- Pass by lvalue reference — LearnCpp — Mekanisme pass by reference beserta konvensi const reference.
- Pass by address — LearnCpp — Pengiriman argumen melalui pointer dan pemeriksaan nullnya.
- Value parameters — LearnCpp — Parameter sebagai salinan nilai argumen pemanggil.