const adalah janji ke compiler: nilai atau objek ini tidak akan diubah. Janji ini bukan sekadar dokumentasi — kompiler menolak kode yang melanggarnya, sehingga bug mutasi tak sengaja tertangkap saat kompilasi. Lebih dari itu, const memberi sinyal optimisasi: compiler tahu bahwa data tidak berubah, dapat meng-cache nilai di register, atau menghapus pemanggilan fungsi berulang. Pada Chapter ini kita akan memetakan empat tempat const dipakai (variabel, parameter, return, method), perbedaan const T* vs T* const, serta manfaat praktis: keselamatan dan performa.

Empat Tempat const Dipakai #

LokasiSintaksArti
Variabel lokalconst int k = 100;k tidak bisa di-assign ulang
Parametervoid f(const T& x)x tidak bisa diubah dalam fungsi
Return valueconst T& get() constPemanggil tidak bisa mutasi hasil
Methodint get() constMethod tidak mutasi *this

Mekanisme: Const sebagai Bagian dari Tipe #

const adalah bagian dari tipe — const int dan int adalah tipe berbeda. Compiler menolak mengkonversi T* ke const T* secara diam-diam? Sebaliknya — konversi T* → const T* aman (menambahkan restriksi), tetapi sebaliknya butuh const_cast (menghapus restriksi — berbahaya bila objek asli benar-benar const).

Untuk method, const setelah parameter (mis. int size() const) menandai method tidak memodifikasi *this. Hanya method const yang bisa dipanggil pada objek const. Method non-const tidak dapat dipanggil — kompiler menolak dengan error “discards qualifiers”.

Kode: Empat Aspek const #

cpp
#include <iostream>
#include <vector>
#include <string>

// 1) const parameter — read-only reference
void print_pair(const std::pair<int, std::string>& p) {
    // p.first = 99;  // ERROR: p adalah const reference
    std::cout << "(" << p.first << ", \"" << p.second << "\")\n";
}

// 2) const return value
const std::string& get_name(const std::vector<std::string>& v, size_t i) {
    return v.at(i);  // return const ref; pemanggil tidak bisa mutasi
}

class Counter {
    int n_ = 0;
public:
    void inc() { ++n_; }
    int get() const { return n_; }    // const method — tidak mutasi state
    int& ref() { return n_; }         // non-const method — bisa mutasi
};

// 3) const_iterator
void print_first_three(const std::vector<int>& v) {
    // v.push_back(0);  // ERROR: v adalah const reference
    auto it = v.cbegin();  // const_iterator — elemen read-only
    for (int i = 0; i < 3 && it != v.cend(); ++i, ++it) {
        std::cout << *it << ' ';
    }
    std::cout << '\n';
}

int main() {
    std::pair<int, std::string> p{42, "answer"};
    print_pair(p);

    std::vector<std::string> names = {"alice", "bob", "carol"};
    const std::string& nm = get_name(names, 1);
    std::cout << "name[1] = " << nm << '\n';
    // nm[0] = 'X';  // ERROR: nm adalah const reference

    Counter c;
    c.inc();
    c.inc();
    std::cout << "counter.get() = " << c.get() << '\n';

    // Bila objek const, hanya method const yang bisa dipanggil
    const Counter& cc = c;
    std::cout << "cc.get() = " << cc.get() << '\n';
    // cc.inc();   // ERROR: cc const, inc() non-const
    // cc.ref() = 99;  // ERROR: ref() non-const

    // const_iterator
    std::vector<int> v = {10, 20, 30, 40, 50};
    print_first_three(v);

    // 4) const local variable
    const int k = 100;
    // k = 200;  // ERROR: k const
    std::cout << "k = " << k << '\n';

    // 5) const pointer vs pointer to const
    int a = 5, b = 7;
    const int* ptr_to_const = &a;   // pointer ke const int
    // *ptr_to_const = 99;            // ERROR: tidak bisa mutasi *ptr
    ptr_to_const = &b;              // OK: bisa re-point
    int* const const_ptr = &a;     // const pointer ke int
    *const_ptr = 99;                // OK: bisa mutasi *const_ptr
    // const_ptr = &b;                // ERROR: pointer sendiri const

    std::cout << "a=" << a << ", b=" << b << ", *ptr_to_const=" << *ptr_to_const
              << ", *const_ptr=" << *const_ptr << '\n';

    return 0;
}

Output yang Dihasilkan:

text
(42, "answer")
name[1] = bob
counter.get() = 2
cc.get() = 2
10 20 30 
k = 100
a=99, b=7, *ptr_to_const=7, *const_ptr=99

Bedah Kode Baris-per-Baris #

  1. void print_pair(const std::pair<int, std::string>& p): parameter const & — fungsi menerima referensi tanpa copy dan berjanji tidak memutasi p. Bila di dalam tubuh fungsi terdapat p.first = 99, kompiler menolak. Inilah kontrak yang dimanfaatkan pemanggil.
  2. const std::string& get_name(const std::vector<std::string>& v, size_t i): parameter dan return keduanya const &. Pemanggil const std::string& nm = get_name(...) mendapat referensi ke elemen names[1] tanpa copy; mencoba nm[0] = 'X' ditolak kompiler.
  3. int get() const { return n_; }: method const — berjanji tidak memutasi *this. Wajib bila ingin dipanggil pada objek const (mis. const Counter& cc).
  4. int& ref() { return n_; }: method non-const karena mengembalikan referensi non-const ke member. Memanggil cc.ref() ditolak kompiler karena cc const.
  5. const Counter& cc = c;: alias const ke objek c. Hanya method const (get()) yang bisa dipanggil. inc() dan ref() ditolak.
  6. v.cbegin(): mengembalikan const_iterator — elemen di posisi tersebut bersifat const int. Sangat berguna bila fungsi menerima const std::vector<T>& dan ingin iterasi tanpa risiko mutasi.
  7. const int k = 100;: variabel lokal const. Kompiler menolak k = 200. Berguna untuk konstanta yang diberi nama (lebih ekspresif daripada magic number, lebih aman daripada #define).
  8. const int* ptr_to_const: pointer ke const int — *ptr tidak bisa dimutasi, tetapi ptr sendiri bisa di-assign ulang ke alamat lain. Baca kiri-ke-kanan: “pointer to const int”.
  9. int* const const_ptr: const pointer ke int — ptr tidak bisa di-assign ulang, tetapi *ptr bisa dimutasi. Baca: “const pointer to int”.
  10. Output a=99: *const_ptr = 99 berhasil — a diubah. b=7 karena ptr_to_const di-assign ulang ke &b, tetapi tidak mengubah b. *ptr_to_const=7 (membaca b) dan *const_ptr=99 (membaca a).

Manfaat Praktis #

1. Bug Detection Lebih Awal #

const menangkap mutasi tak sengaja saat kompilasi:

cpp
void process(const std::vector<int>& data) {
    data.push_back(0);   // ERROR — caught at compile time
}

2. Sinyal Optimisasi #

Compiler tahu objek const tidak akan berubah → bisa meng-cache nilai:

cpp
size_t total_size(const std::vector<std::string>& v) {
    size_t s = 0;
    for (const auto& str : v) s += str.size();
    return s;
}

Karena v const, compiler tahu tidak ada alias yang bisa memodifikasi v selama loop → bisa mengoptimalkan pemanggilan .size().

3. Kontrak API #

Parameter const T& adalah kontrak: “saya hanya membaca, tidak akan mengubah data Anda”. Pemanggil aman melewatkan objek berharga tanpa khawatir disabotase.

4. Compatibility dengan Container Const #

Container const hanya menyediakan const_iterator via begin()/end(). Bila Anda memakai auto it = v.begin() pada const vector, it otomatis const_iterator. Untuk eksplisit: cbegin()/cend().

Jebakan: const_cast #

const_cast<T&>(x) menghapus const. Ini legal hanya bila objek asli benar-benar non-const. Bila objek asli const (mis. const int k = 100 lalu const_cast<int&>(k) = 99), hasilnya undefined behavior. Hindari const_cast kecuali berurusan dengan API C lama.

Ringkasan #

  • const adalah bagian dari tipe — kompiler menolak mutasi objek const.
  • Empat lokasi: variabel lokal, parameter, return value, method.
  • Method const hanya bisa dipanggil pada objek const; method non-const ditolak.
  • const T* (pointer to const) berbeda dari T* const (const pointer) — baca dari kanan ke kiri.
  • const memungkinkan optimisasi (cache nilai, eliminate call) dan menangkap bug mutasi saat kompilasi.
  • Hindari const_cast kecuali untuk API C; hapus const pada objek yang benar-benar const adalah UB.
  • Aturan praktis: tulis const di mana pun bisa, hapus hanya bila terbukti perlu.