Topik ini membahas member static, yaitu data dan fungsi yang dimiliki kelas sebagai satu kesatuan dan tidak melekat pada objek tertentu. Materinya mencakup data member static beserta definisinya di luar kelas, fungsi member static yang dapat dipanggil tanpa membuat objek, serta kasus penggunaan yang wajar bagi keduanya. Setelah membacanya, keadaan yang dibagi seluruh objek dan utilitas milik kelas dapat ditempatkan pada tempat yang tepat tanpa mengorbankan enkapsulasi.
Data member static#
Data member static berjumlah tepat satu untuk setiap kelas, berapa pun objeknya yang sedang hidup. Member biasa disalin pada setiap objek, sedangkan member static tinggal satu di tingkat kelas dan dibagi seluruh objek yang membaca maupun menulisnya. Karena itu member static tidak dihitung oleh sizeof atas objek, dan masa hidupnya dimulai sejak program berjalan, bukan sejak objek pertama dibuat. Deklarasinya di dalam kelas diawali keyword static.
Deklarasi di dalam kelas hanya menyatakan keberadaan member tersebut, sehingga satu definisi di luar kelas tetap diperlukan. Definisi itu ditulis sekali pada satu berkas dengan bentuk int NamaKelas::nama = nilai;. Program penghitung objek berikut menggunakan pola ini untuk mencatat jumlah objek yang sedang hidup.
#include <iostream>
class PenghitungObjek {
private:
static int jumlah;
public:
PenghitungObjek() {
jumlah++;
}
~PenghitungObjek() {
jumlah--;
}
static int bacaJumlah() {
return jumlah;
}
};
int PenghitungObjek::jumlah = 0;
int main() {
std::cout << "Awal : " << PenghitungObjek::bacaJumlah() << "\n";
PenghitungObjek a;
PenghitungObjek b;
std::cout << "Dua objek : " << PenghitungObjek::bacaJumlah() << "\n";
{
PenghitungObjek c;
std::cout << "Tiga objek : " << PenghitungObjek::bacaJumlah() << "\n";
}
std::cout << "Akhir : " << PenghitungObjek::bacaJumlah() << "\n";
return 0;
}Awal : 0
Dua objek : 2
Tiga objek : 3
Akhir : 2Setiap constructor menaikkan penghitung dan setiap destructor menurunkannya, sehingga angkanya mengikuti jumlah objek yang sedang hidup. Ketika blok dalam main berakhir, objek c dihancurkan dan jumlahnya turun menjadi dua, sedangkan a dan b baru dihancurkan setelah main selesai. Penghitungan semacam ini tidak mungkin disimpan pada member biasa, karena member biasa memiliki salinan sendiri di setiap objek dan tidak dapat melihat objek lain.
Definisi di luar kelas juga boleh ditulis tanpa inisialisasi, dan nilainya menjadi nol karena data tersebut berada pada durasi penyimpanan static. Inisialisasi eksplisit seperti = 0 pada program di atas tetap dianjurkan agar niatnya terbaca. Adapun definisi yang ditulis dua kali pada dua berkas berbeda akan ditolak linker sebagai definisi ganda.
Fungsi member static#
Fungsi member static dideklarasikan dengan static dan tidak terikat pada objek mana pun, sehingga tidak memiliki pointer this. Ketiadaan this membuat fungsi semacam ini hanya dapat mengakses data static dan fungsi static lain, bukan member biasa yang salinannya dimiliki setiap objek. Pemanggilannya dilakukan lewat nama kelas dengan bentuk NamaKelas::fungsi(), dan tetap sah pula lewat objek meskipun bentuk itu menyesatkan pembaca karena objeknya tidak berperan apa pun. Fungsi member static juga tidak dapat dinyatakan virtual, karena pemilihan versi fungsi secara virtual memerlukan objek saat program berjalan, sedangkan fungsi static tidak terikat pada objek apa pun.
Program berikut menempatkan dua fungsi konversi suhu pada kelas KonverterSuhu. Kedua fungsi itu tidak menyimpan maupun mengubah keadaan apa pun, sehingga pemanggilan lewat nama kelas menyatakan niatnya dengan tepat.
#include <iostream>
class KonverterSuhu {
public:
static double celciusKeFahrenheit(double c) {
return c * 9.0 / 5.0 + 32.0;
}
static double fahrenheitKeCelcius(double f) {
return (f - 32.0) * 5.0 / 9.0;
}
};
int main() {
std::cout << "100 C = " << KonverterSuhu::celciusKeFahrenheit(100.0) << " F\n";
std::cout << "32 F = " << KonverterSuhu::fahrenheitKeCelcius(32.0) << " C\n";
std::cout << "36.6 C = " << KonverterSuhu::celciusKeFahrenheit(36.6) << " F\n";
return 0;
}100 C = 212 F
32 F = 0 C
36.6 C = 97.88 FKeluaran memperlihatkan konversi dua arah beserta nilai desimal yang tetap tercetak benar. Konversi matematis semacam ini memang tidak menyimpan keadaan, sehingga menjadikannya member static memisahkan utilitas dari data objek tanpa perlu menempatkannya sebagai fungsi bebas yang berdiri sendiri di luar kelas.
Kasus penggunaan yang wajar#
Member static cocok untuk keadaan dan layanan yang memang milik kelas, bukan milik satu objek. Beberapa pola yang lazim ditemui pada kode nyata antara lain berikut ini.
- Penghitung objek hidup untuk diagnostik, seperti
PenghitungObjekpada program pertama. - Konstanta bersama kelas, misalnya batas suhu minimum yang berlaku untuk setiap objek
PengaturSuhu. - Fungsi utilitas yang tidak memerlukan keadaan objek, seperti konversi satuan pada kelas
KonverterSuhu. - Data konfigurasi yang dibaca semua objek tanpa mengulang penyimpanannya pada setiap objek.
Member static mengungguli variabel global untuk keperluan tersebut karena tetap tunduk pada access specifier. Data yang dinyatakan private tidak dapat disentuh kode luar, sehingga keadaan bersama tetap terjaga enkapsulasinya. Ruang lingkup namanya juga terikat pada kelasnya, sehingga ruang nama global tidak bertambah penuh dan berkas lain tidak mewarisi keberadaannya secara diam-diam. Meskipun demikian, data static yang berubah-ubah perlu digunakan secara hemat, karena setiap bagian program yang membacanya melihat nilai yang sama dan urutan perubahannya sulit dilacak apabila titik penulisannya terlalu banyak.
Dua bentuk pemanggilan fungsi static berikut menunjukkan bahwa objek tidak berperan dalam pemanggilannya.
// tidak-dijalankan (fragmen tanpa main, hanya untuk menunjukkan dua bentuk pemanggilan)
PenghitungObjek a;
int lewatKelas = PenghitungObjek::bacaJumlah();
int lewatObjek = a.bacaJumlah();Kesalahan umum#
Tiga kekeliruan berikut paling sering muncul saat menggunakan member static.
- Melupakan definisi data member static di luar kelas. Kompilasi berhasil, tetapi linker mengeluarkan error undefined reference saat program disatukan. Perbaikannya adalah menambahkan satu baris definisi, misalnya
int PenghitungObjek::jumlah = 0;. - Mengakses member non-static dari dalam fungsi static. Compiler menolaknya karena fungsi static tidak memiliki
this. Perbaikannya adalah mengoper objek sebagai parameter atau mengubah fungsi tersebut menjadi member biasa. - Menyimpan keadaan yang seharusnya milik objek pada data static yang berubah-ubah. Semua objek melihat nilai yang sama, sehingga perilakunya saling mengganggu. Perbaikannya adalah memindahkan keadaan itu ke member biasa.
Ringkasan#
- Data member static berjumlah satu untuk seluruh kelas, tidak menjadi bagian objek mana pun, dan tetap memerlukan definisi di luar kelas.
- Fungsi member static tidak memiliki
this, sehingga hanya mengakses member static dan dipanggil lewat nama kelas. - Pola yang wajar mencakup penghitung objek, konstanta bersama, dan fungsi utilitas tanpa keadaan objek.
- Member static tetap tunduk pada access specifier, sehingga keadaan bersama dapat dijaga tetap
private.
Referensi#
- Static members — cppreference — Aturan lengkap data dan fungsi static, termasuk definisi di luar kelas.
- Static member variables — LearnCpp — Pengantar data member static beserta inisialisasi dan definisinya.
- Static member functions — LearnCpp
— Fungsi static tanpa
thisbeserta batasan aksesnya.