Kita telah menempuh perjalanan panjang: dari byte dan alamat, pointer dan dereferencing, nullptr, aritmetika pointer, reference, variasi const, pointer sebagai parameter, pointer-to-pointer, void*, hingga pointer-pointer bermasalah. Chapter penutup ini menyandingkan dua alat utamanya — pointer dan reference — dalam satu bingkai. Keduanya sama-sama memungkinkan kita bekerja dengan objek lewat jalur tak langsung, tetapi filosofinya berbeda: pointer adalah variabel berisi alamat yang hidupnya sendiri, reference adalah nama kedua yang melekat pada objek lain. Memilih dengan tepat membuat kode lebih jelas, lebih aman, dan lebih mudah diperiksa.

Sintaks dan Semantik dalam Satu Program #

Program kecil berikut memakai keduanya untuk pekerjaan yang sama — menukar dua nilai — sehingga perbedaannya terlihat berhadapan langsung:

cpp
#include <iostream>

// Versi pointer: caller mengirim alamat, fungsi melakukan dereference
void tukarDenganPointer(int* a, int* b) {
    int sementara = *a;
    *a = *b;
    *b = sementara;
}

// Versi reference: caller cukup mengirim variabelnya
void tukarDenganReference(int& a, int& b) {
    int sementara = a;
    a = b;
    b = sementara;
}

int main() {
    int x = 3;
    int y = 8;

    tukarDenganPointer(&x, &y);
    std::cout << "Pointer   : x = " << x << ", y = " << y << "\n";

    tukarDenganReference(x, y);
    std::cout << "Reference : x = " << x << ", y = " << y << "\n";

    // Perbedaan kunci: pointer boleh kosong, reference selalu terikat
    int* p = &x;
    int& r = x;
    p = nullptr;  // sah untuk pointer
    std::cout << "p boleh kosong   : " << (p == nullptr) << "\n";
    std::cout << "r terikat pada x : " << r << "\n";

    return 0;
}

Output yang Dihasilkan:

text
Pointer   : x = 8, y = 3
Reference : x = 3, y = 8
p boleh kosong   : 1
r terikat pada x : 3

Yang perlu kita amati:

  1. Baris 4-9 (tukarDenganPointer): badan fungsi penuh tanda *, dan pemanggilnya harus menulis &x, &y. Setiap akses nilai butuh dereference eksplisit — tidak ada yang disembunyikan, tetapi juga tidak ada yang dilindungi.
  2. Baris 12-17 (tukarDenganReference): badan fungsi persis seperti versi dengan variabel biasa, dan pemanggilnya menulis x, y saja. Detail alamat sepenuhnya urusan compiler.
  3. Baris 22-26: kedua fungsi menghasilkan pekerjaan yang benar — nilai kembali ke urutan semula setelah dua kali ditukar. Untuk kasus sederhana seperti ini, versi reference lebih ringkas dan lebih sulit salah.
  4. Baris 29-34: perbedaan semantik yang tak tersembunyikan. p dengan bebas dilepas sasarannya (p = nullptr), sedangkan r tetap terikat pada x sampai habis masa hidupnya — reference tidak mengenal keadaan kosong.

Tabel Perbandingan Lengkap #

AspekPointerReference
Deklarasiint* p = &x;int& r = x;
Inisialisasiopsional (berisiko)wajib saat deklarasi
Akses nilaidereference: *plangsung: r
Ganti sasaran (rebind)ya — p = &lain;tidak — ikatan permanen
Tanpa sasaranya — nullptrtidak ada keadaan kosong
Arithmeticada — p + 1 bergerak per elementidak ada
Aritas alamat&p menghasilkan alamat pointer&r menghasilkan alamat objek yang di-alias
Dapat bertingkatya — int** dan seterusnyatidak
Pemeriksaan sebelum pakaiperlu cek nulltidak perlu
Konsttiga variasi (pointer-to-const, const pointer, keduanya)satu bentuk: const int&
Penulisan ulang kode warisan Cdidukung penuhtidak ada di C

Baris “aritas alamat” patut digarisbawahi karena sering muncul di ujian dan code review: &r bukan alamat “variabel r” — compiler menganggapnya alamat x, sebab r hanyalah nama kedua.

Panduan Memilih #

Pengalaman komunitas C++ merangkum pilihan ini menjadi aturan yang sederhana: reference sebagai pilihan bawaan, pointer hanya bila kekosongan atau penggantian sasaran memang bagian dari desain.

Pilih reference ketika:

  • Parameter fungsi wajib ada dan tidak berpindah-pindah — terutama objek besar yang tidak mau kita salin (const std::string&, const std::vector<int>&).
  • Fungsi perlu menulis hasil ke objek pemanggil dan kekosongan bukan keadaan yang bermakna.
  • Kita ingin pemeriksaan null benar-benar dihilangkan dari kode, bukan sekadar jarang gagal.

Pilih pointer ketika:

  • “Tidak ada” adalah kondisi yang sah: hasil pencarian yang gagal, opsi yang belum diisi, dependensi yang bisa dilepas.
  • Sasarannya perlu diganti sepanjang masa hidup — misalnya penunjuk posisi berjalan di dalam array lewat pointer arithmetic.
  • Kita mengelola memori dinamis atau berinteraksi dengan API C yang menuntut T*, void*, atau T**.
  • Struktur data menuntutnya: linked list, tree, atau array of pointers menyimpan “alamat yang bisa kosong” sebagai bagian dari desainnya.

Penutup Section Pointer dan Reference #

Dengan dua alat ini, kita kini menguasai cara kerja memori secara sadar: meminta alamat, menyimpannya, mengunjunginya, menghitungnya, menguncinya dengan const, dan menjaga kode dari pointer yang bermasalah. Kemampuan ini adalah pintu masuk sebagian besar materi berikutnya: iterator pada Standard Library bergerak persis seperti pointer arithmetic, smart pointer pada Section Manajemen Memori adalah pointer yang dijaga otomatis, dan polymorphism pada Section Pemrograman Berorientasi Objek bekerja melalui pointer dan reference ke objek.

Langkah berikutnya di Section Struktur dan Tipe Data Komposit adalah menata beberapa data dalam satu wadah yang kita rancang sendiri — struct, enum, dan union — sehingga alamat yang selama ini menunjuk satu int akan menunjuk objek yang lebih kaya isinya.

Referensi: cppreference — reference declaration , cppreference — pointer declaration .