Sepanjang section ini kita berkali-kali menyebut scope — “sisa hanya hidup di dalam if”, “variabel loop tidak bocor ke luar”, “diskon tidak lagi tersedia di sini”. Sekarang saatnya membahas aturan itu secara tuntas, karena ia mengatur SETIAP variabel yang pernah kita tulis. Ada dua pertanyaan berbeda yang sering tertukar: scope menjawab di wilayah kode mana sebuah nama boleh dipakai, sedangkan lifetime menjawab berapa lama objeknya benar-benar ada di memori. Pada Chapter ini kita memetakan tiga jenjang scope, menyaksikan fenomena shadowing yang bisa menipu mata, membedah lifetime objek otomatis versus variabel static lokal, lalu menutup section ini dengan prinsip praktisnya.

Tiga Jenjang Scope #

C++ mengatur keterlihatan nama berdasarkan blok — wilayah di antara sepasang kurung kurawal. Dari dalam ke luar, ada tiga jenjang yang perlu kita pegang:

  • Block scope: variabel yang dideklarasikan di dalam { } — badan if, badan loop, blok bebas, atau badan fungsi — hanya terlihat dari titik deklarasinya sampai kurung kurawal penutup blok itu.
  • Function scope untuk parameter: parameter fungsi berperilaku seperti variabel blok milik fungsi tersebut; fungsi lain tidak bisa melihatnya. Untuk variabel lokal di badan fungsi, block scope tetap berlaku.
  • File scope (global): variabel yang dideklarasikan DI LUAR semua fungsi terlihat oleh seluruh kode di file itu, dari titik deklarasinya sampai akhir file.

Program berikut menampilkan ketiganya sekaligus, termasuk blok bebas — sepasang kurung kurawal yang berdiri sendiri di dalam fungsi:

cpp
#include <iostream>

int harga = 1000;   // file scope (global)

void tampilkanHarga() {
    int harga = 500;   // shadowing: menutupi harga global di dalam fungsi ini
    std::cout << "Harga lokal di fungsi: " << harga << "\n";
}

int main() {
    tampilkanHarga();
    std::cout << "Harga global di main: " << harga << "\n";

    {
        int diskon = 200;   // block scope: hidup hanya di blok ini
        std::cout << "Harga setelah diskon: " << harga - diskon << "\n";
    }
    // diskon tidak lagi tersedia di sini

    return 0;
}

Output yang Dihasilkan:

text
Harga lokal di fungsi: 500
Harga global di main: 1000
Harga setelah diskon: 800

Mari kita bedah:

  1. Baris 4 (int harga = 1000;): deklarasi di luar semua fungsi membuat harga bertipe global — terlihat oleh tampilkanHarga() maupun main().
  2. Baris 7 (int harga = 500;): fungsi mendeklarasikan variabel lokal dengan NAMA SAMA. Di dalam fungsi itu, nama harga menunjuk ke versi lokal — fenomena shadowing yang kita bedah di bagian berikut.
  3. Baris 13-15 (blok bebas di main): diskon dideklarasikan di dalam blok dan dipakai satu kali di sana. Blok ini tidak milik if maupun loop — kurung kurawal boleh berdiri sendiri semata untuk membatasi scope.
  4. Baris 17 (komentar): sesudah kurung penutup, diskon tidak ada lagi. Mencoba std::cout << diskon; di baris ini ditolak compiler dengan pesan “not declared” — galat yang sekarang kita pahami akar penyebabnya. Definisi resmi scope dalam standar C++ tersaji di cppreference .

Shadowing: Nama yang Menutupi Nama #

Shadowing terjadi saat variabel di scope dalam memakai nama yang sama dengan variabel di scope luar. Kode tetap sah — compiler menyanggupi — tetapi di wilayah dalam, nama itu merujuk versi dalam sepenuhnya; versi luar tidak dapat dicapai lewat nama itu lagi. Pada program di atas, tampilkanHarga() mencetak 500 (versi lokalnya), sementara main() mencetak 1000 (versi global) — dua variabel yang benar-benar berbeda memori, hanya kebetulan sepasang nama. Efek shadowing bisa membekas sampai ribuan baris kemudian: programmer membaca kode dan menyangka sebuah nama merujuk variabel yang terlihat di layar, padahal ada versi lain yang lebih dekat dan menang.

Satu kasus shadowing yang perlu diwaspadai secara khusus: variabel loop yang menutupi variabel luar dengan nama sama, misalnya dua i — satu global, satu milik loop. Loop dalam tidak menaikkan i luar karena yang dinaikkan miliknya sendiri; logika luar pun berjalan meleset tanpa galat apa pun. Aturan penamaan yang disiplin sejak awal lebih murah daripada mencari bug semacam ini.

Lifetime: Sampai Kapan Objek Ada #

Scope mengatur keterlihatan nama; lifetime mengatur umur objek di memori. Untuk variabel yang kita pakai selama ini — yang disebut automatic storage duration — keduanya berjalan seiring: objek dibuat saat deklarasinya dieksekusi, dan dibongkar saat bloknya berakhir. Itulah alasan diskon tidak hanya tak terlihat, melainkan benar-benar tidak ada lagi sesudah bloknya; setiap pemanggilan fungsi juga memulai variabel lokalnya dari nol, persis seperti hitungan pada countdown.cpp yang selalu mulai dari 3. Tetapi C++ menyediakan pengecualian yang menarik: kata kunci static pada variabel lokal membuat objeknya dibuat SEKALI dan bertahan lintas pemanggilan — scopenya tetap lokal (tak terlihat dari luar), tetapi lifetime-nya menyamai program:

cpp
#include <iostream>

void catatKunjungan() {
    static int jumlah = 0;   // bertahan dan terus bertambah antar pemanggilan
    ++jumlah;
    std::cout << "Kunjungan ke-" << jumlah << "\n";
}

int main() {
    catatKunjungan();
    catatKunjungan();
    catatKunjungan();
    return 0;
}

Output yang Dihasilkan:

text
Kunjungan ke-1
Kunjungan ke-2
Kunjungan ke-3

Padahal baris static int jumlah = 0; tampak seperti deklarasi biasa yang seharusnya me-reset ke 0 setiap pemanggilan. Kenyataannya: inisialisasi static lokal dieksekusi tepat satu kali pada pemanggilan pertama; tiga pemanggilan berikutnya melompatinya dan terus menaikkan nilai yang tersisa. Ini membuat static lokal berguna untuk counter pemanggilan, cache kecil, atau state yang memang harus lintas panggilan — dengan catatan bahwa ia memperkenalkan memori yang “ingin mengingat”, sehingga perlu dipakai sadar. Pemakaian static di tingkat class (anggota yang dibagi antar semua object) menanti pada Chapter Static Members di Section Pemrograman Berorientasi Objek.

Praktik Terbaik: Scope Sekecil Mungkin #

Dari semua aturan di atas, satu prinsip layak dibawa pulang dari section ini: deklarasikan variabel pada scope terkecil yang masih memenuhi kebutuhan, sedekat mungkin dengan pemakaiannya. Prinsip ini merangkum semua yang sudah kita buktikan:

  • Lebih sedikit nama yang bersaing: variabel yang mati cepat tidak mungkin bentrok dengan variabel lain — risiko shadowing turun.
  • Kesalahan terdeteksi lebih awal: memakai variabel di luar bloknya ditolak compiler, bukan lolos sebagai bug runtime.
  • Pembacaan lebih mudah: pembaca tidak perlu mengingat variabel dari ratusan baris di atas; konteksnya ada di tempat deklarasi.

Dengan scope dan lifetime tuntas, section Control Flow juga menutup tirainya: kita kini bisa membuat program bercabang, berulang, dan berstruktur dengan penuh kesadaran akan apa yang terjadi pada setiap variabelnya. Pertanyaan berikutnya yang menggantung sejak Chapter return — bagaimana cara mengemas pekerjaan ke dalam fungsi yang bisa dipakai ulang — menjadi pintu masuk section berikutnya: Fungsi.