Pada Chapter sebelumnya kita menandai semua member Kotak dengan public: sehingga kode mana pun bebas mengubah datanya. Kondisi seperti itu berbahaya untuk class yang menyimpan state penting: satu baris rekening.saldo = -999999; dari bagian lain program bisa merusak konsistensi data. C++ menjawab masalah ini dengan access specifiers — kata kunci yang mengatur siapa yang boleh mengakses member tertentu. Chapter ini membahas public, private, dan protected, termasuk perbedaan default antara class dan struct.

Tiga Access Specifiers #

Access specifier ditulis di dalam badan class diikuti titik dua, misalnya private:. Semua member yang dideklarasikan setelahnya mengikuti tingkat akses tersebut, sampai ada specifier berikutnya yang menggantinya. Ada tiga tingkat akses di C++:

SpecifierSiapa yang boleh mengaksesBiasa dipakai untuk
publicKode mana pun, dari dalam maupun luar classAntarmuka class: fungsi yang boleh dipanggil pengguna
privateHanya member function class itu sendiri (dan friend)Data member dan fungsi internal
protectedClass itu sendiri plus class turunannya (inheritance)Data yang perlu diwariskan nanti

Specifiers boleh muncul lebih dari sekali dan boleh berganti-ganti urutan di dalam satu class. Sebuah class bisa dibuka public: untuk beberapa fungsi, lalu ditutup private: untuk data, lalu dibuka lagi public: untuk fungsi lain. Compiler menghormati bagian terakhir yang berlaku pada setiap member, bukan posisi specifier pertama.

Default class = private, Default struct = public #

Satu perbedaan mendasar antara class dan struct ada di member yang tidak diberi specifier. Pada class, member tanpa label otomatis bersifat private; pada struct, member tanpa label otomatis bersifat public. Dua definisi berikut keduanya sah dan perilakunya sama persis:

cpp
class Kotak {
    double panjang;   // private, karena ada di dalam class
};

struct Kotak2 {
    double panjang;   // public, karena ada di dalam struct
};

Lalu kapan memakai yang mana? Konvensi yang lazim di komunitas C++: pakai struct untuk kumpulan data sederhana yang semua anggotanya memang terbuka (misalnya koordinat Titik atau baris hasil query), dan pakai class bila ada aturan yang harus dijaga terhadap datanya. Perbedaan keduanya murni soal akses default ini — setelah kita menuliskan specifier secara eksplisit, class dan struct setara dalam hampir semua aspek.

Mengapa Data Perlu Disembunyikan #

Menyembunyikan data member (information hiding) adalah inti dari encapsulation. Ketika saldo hanya bisa diubah lewat setor() dan tarik(), kita menjamin setiap perubahan saldo melewati aturan yang sama: tidak boleh menarik melebihi saldo, tidak ada pengurangan negatif yang tak tercatat. Aturan semacam ini disebut invariant — kondisi yang harus selalu benar tentang data class. Bila data terbuka untuk semua, kita tidak punya tempat tunggal untuk menegakkan invariant tersebut.

Manfaat kedua adalah kebebasan berubah. Selama antarmuka public tidak diubah, kita bebas merombak isi private — mengganti tipe data, menambah cache, atau menghitung nilai on the fly — tanpa memengaruhi satu baris pun kode pengguna class. Inilah alasan desainer class profesional menaruh hampir semua data member di bagian private dan mengekspos fungsi yang benar-benar dibutuhkan saja. Ingat juga bahwa access specifier adalah alat desain untuk mencegah kesalahan, bukan mekanisme keamanan melawan kode jahat.

Contoh: Rekening Bank dengan Data Tersembunyi #

Program berikut menyembunyikan saldo dan pemilik, lalu menyediakan antarmuka public yang menjaga aturan penarikan:

cpp
#include <iostream>
#include <string>

class RekeningBank {
private:                    // section private: data disembunyikan dari luar
    std::string pemilik;
    long long saldo;        // hanya boleh diubah lewat method public

public:                     // section public: antarmuka class
    void buka(const std::string& nama, long long saldoAwal) {
        pemilik = nama;
        saldo = saldoAwal;
    }

    void setor(long long jumlah) {
        saldo += jumlah;
    }

    bool tarik(long long jumlah) {
        if (jumlah > saldo) {
            std::cout << "Saldo tidak cukup untuk " << pemilik << "\n";
            return false;
        }
        saldo -= jumlah;
        return true;
    }

    void cetakSaldo() {
        std::cout << "Saldo " << pemilik << ": Rp" << saldo << "\n";
    }
};

struct Titik {              // default struct = public
    int x;
    int y;
};

int main() {
    RekeningBank rekening;
    rekening.buka("Budi", 500000);
    rekening.setor(150000);
    rekening.tarik(200000);
    rekening.cetakSaldo();

    rekening.tarik(900000); // ditolak oleh aturan di dalam class
    rekening.cetakSaldo();

    Titik titik;
    titik.x = 3;            // sah: member struct default-nya public
    titik.y = 4;
    std::cout << "Titik: " << titik.x << "," << titik.y << "\n";

    return 0;
}

Output yang Dihasilkan:

text
Saldo Budi: Rp450000
Saldo tidak cukup untuk Budi
Saldo Budi: Rp450000
Titik: 3,4

Bedah singkatnya:

  1. Baris 5-7 (private: ...): pemilik dan saldo berada di section private. Tidak ada kode di luar class yang bisa membaca atau menuliskannya secara langsung.
  2. Baris 16-19 (bool tarik(...)): seluruh logika penarikan — termasuk pemeriksaan kecukupan saldo — hidup di dalam class. Invariant “saldo tidak boleh minus” kini hanya dijaga di satu tempat.
  3. Baris 33-34 (rekening.buka(...); rekening.setor(...)): main() hanya berinteraksi lewat antarmuka public. Ia tidak tahu (dan tidak perlu tahu) bagaimana saldo disimpan.
  4. Baris 40-41 (rekening.tarik(900000)): penarikan melebihi saldo ditolak oleh class itu sendiri, dan saldo tetap 450000.
  5. Baris 45-47 (Titik titik; titik.x = 3;): pengisian member struct langsung seperti ini sah karena default-nya public — pola yang wajar untuk data sederhana.

Apa yang Terjadi Bila Tetap Memaksa #

Bila kita mencoba mengubah member private langsung dari luar class, compiler akan menolaknya sebelum program jadi. Potongan berikut sengaja kita buat salah untuk menunjukkan pesannya:

cpp
RekeningBank rekening;
rekening.saldo = 999999;   // member private: compiler menolak
text
private-demo.cpp:11:14: error: 'long long int RekeningBank::saldo' is private within this context
   11 |     rekening.saldo = 999999;   // member private: compiler menolak
      |              ^~~~~
private-demo.cpp:6:15: note: declared private here
    6 |     long long saldo;
      |               ^~~~~

Pesan is private within this context menunjukkan access specifier bekerja pada saat kompilasi, bukan saat program berjalan. Kesalahan akses semacam ini tidak pernah sempat menjadi bug runtime — compiler menahannya di gerbang. Bila kita benar-benar butuh mengubah saldo, jalannya tetap lewat fungsi public yang menyediakan jalur sah.

Kita kini bisa menyembunyikan data dan membuka antarmuka. Chapter berikutnya merapikan sisa kerangkanya: bagaimana mendeklarasikan data member dan member function secara lengkap, termasuk memberi nilai awal langsung saat deklarasi.

Rujukan resmi: Member access (cppreference) , Classes (cppreference) .