Chapter sebelumnya berakhir dengan dua luka pada unscoped enum: nama enumerator yang bocor ke scope sekitar dan konversi implisit ke int yang lolos tanpa suara. C++11 memperkenalkan obatnya dengan kata kunci tambahan class — bentuk yang disebut scoped enum atau enum class. Namanya tetap menunjuk deretan konstanta bernama, tetapi compiler kini menjaga batas-batasnya dengan ketat. Pada Chapter ini kita menulis enum class pertama, membuktikan bahwa enumerator dua enum berbeda tidak lagi bertabrakan, mengeluarkan nilai numeriknya secara eksplisit lewat static_cast, dan menetapkan underlying type sendiri untuk menghemat memori.
Sintaks enum class dan Scope Tertutup #
Perbedaan sintaksnya kecil: tambahkan class setelah enum, lalu setiap penggunaan enumerator harus melewati nama enum-nya dengan operator :: — misalnya Status::Menunggu. Aturan :: ini bukan sekadar formalitas; ia menegaskan bahwa enumerator hidup di dalam scope enum, bukan di scope sekitarnya.
#include <iostream>
#include <cstdint>
// Scoped enum: enumerator hanya hidup di dalam scope Status
enum class Status {
Sukses,
Gagal,
Menunggu
};
// Underlying type eksplisit: hemat memori untuk enum yang dibuat jutaan kali
enum class Level : uint8_t {
Rendah,
Sedang,
Tinggi
};
void proses(Status s) {
switch (s) {
case Status::Sukses:
std::cout << "Proses selesai dengan sukses\n";
break;
case Status::Gagal:
std::cout << "Proses berhenti karena gagal\n";
break;
case Status::Menunggu:
std::cout << "Proses masih menunggu\n";
break;
}
}
int main() {
Status kondisi = Status::Menunggu;
proses(kondisi);
// Tidak ada konversi implisit: harus eksplisit lewat static_cast
std::cout << "Nomor status: " << static_cast<int>(kondisi) << "\n";
std::cout << "sizeof(Status) = " << sizeof(Status) << " byte\n";
std::cout << "sizeof(Level) = " << sizeof(Level) << " byte\n";
return 0;
}Output yang Dihasilkan:
Proses masih menunggu
Nomor status: 2
sizeof(Status) = 4 byte
sizeof(Level) = 1 byteMari kita telaah apa yang terjadi pada baris-baris kode di atas:
- Baris 5-9 (
enum class Status { ... }): definisi scoped enum dengan tiga enumerator bernilai implisit 0, 1, 2 — aturan penomorannya sama seperti unscoped enum. - Baris 12-16 (
enum class Level : uint8_t): bentuk dengan underlying type eksplisit. Bagian: uint8_tmenyatakan bahwa enum ini diwakiliuint8_t(unsigned 1 byte), bukanint. - Baris 18-30 (
void proses(Status s)):switchpada enum class bekerja wajar; tiap label case wajib menulisStatus::lengkap. Karena semua enumerator tercakup, tidak ada peringatan compiler tentang case yang terlewat. - Baris 33-34: variabel dan parameter bertipe
Statushanya boleh berisi enumeratorStatus— tidak ada angka lain yang bisa menyusup. - Baris 37-40: nilai numerik dikeluarkan dengan
static_cast<int>(kondisi), dansizeofmemperlihatkan perbedaan ukuran kedua enum.
Bukti scope tertutupnya paling terasa saat dua enum punya enumerator bernama sama. Kode berikut yang tadinya gagal dikompilasi pada unscoped enum kini sah dan bebas konflik:
enum class Warna { Merah, Hijau, Biru };
enum class Lampu { Merah, Kuning, Hijau }; // Merah di sini tidak bertabrakan
Warna kertas = Warna::Merah;
Lampu lampu = Lampu::Merah; // dua konstanta, dua makna, tanpa konflik
Tidak Ada Konversi Implisit #
Scoped enum tidak lagi menyamar menjadi int. Menugaskan nilainya ke variabel int langsung ditolak compiler:
Status s = Status::Gagal;
int n = s; // ditolak compiler
Pesan kesalahannya menjelaskan dengan gamblang:
error: cannot convert 'Status' to 'int' in initializationJika kita memang butuh nilai numeriknya — untuk dicetak, disimpan ke file, atau dikirim lewat protokol — niat itu harus dituliskan eksplisit dengan static_cast<int>(...), operator cast yang sudah kita kenal pada Chapter Konversi Tipe Data. Perbandingan antar enumerator tetap wajar (s == Status::Gagal sah karena satu tipe), tetapi s == 1 kini ditolak: compiler tidak lagi membiarkan angka mentah menyamar sebagai status. Arah sebaliknya, static_cast<Status>(2), juga tersedia untuk kasus membaca data dari file atau jaringan — gunakan dengan sadar, karena nilai 2 di luar daftar enumerator tidak dicek secara runtime.
Menentukan Underlying Type #
Secara bawaan, compiler memilih int sebagai penyimpan enum class, sehingga sizeof(Status) bernilai 4 byte pada platform ini. Ketika enum dibuat dalam jumlah besar — jutaan objek struct yang masing-masing membawa satu status — 4 byte per field terasa boros. Deklarasi enum class Level : uint8_t memaksa penyimpanannya satu byte; output di atas membuktikan sizeof(Level) turun menjadi 1 byte. Tipe yang dipilih haruslah tipe integral (int, unsigned char/uint8_t, long long, dan seterusnya), dan nilai semua enumerator harus muat di dalamnya.
enum class Status : uint8_t { Sukses, Gagal, Menunggu }; // 1 byte
enum class Opsi : int { A, B, C }; // 4 byte (eksplisit)
Kombinasi yang paling sering kita temui di kode embedded dan game engine adalah enum class ... : uint8_t untuk flag kecil, dan underlying type bawaan (int) untuk sisanya. Detail ukuran tipe integral ini sudah kita bahas pada Section Variabel dan Tipe Data; di sini kita hanya memilihnya untuk enum.
Mengapa enum class Lebih Disarankan #
Menyusun perbandingan dua bentuk enum di atas, keunggulan enum class dapat kita petakan secara langsung:
- Nama aman: enumerator tidak memenuhi scope sekitar, sehingga dua enum boleh punya nama enumerator yang sama.
- Tanpa konversi terselubung: tidak ada jalur diam-diam ke/dari
int, sehingga pembandingan dan penugasan lintas makna tertangkap compiler. - Ukuran terkendali: underlying type bisa ditentukan, sesuatu yang juga dimungkinkan pada unscoped enum tetapi jarang dipakai.
- Maksud terbaca:
Status::Gagalmenyebut asal-usulnya, sementaraGagalyang melayang bebas bisa berasal dari enum mana pun.
Mengapa enum class tetap tidak menggantikan unscoped enum sepenuhnya? Jawabannya kompatibilitas: kode C dan kode C++ lama bergantung pada perilaku lama, dan beberapa kasus (misalnya bitmask sederhana) masih nyaman dengan konversi implisit. Untuk kode baru yang kita tulis sekarang, pilih enum class kecuali ada alasan kuat untuk sebaliknya.
Referensi resmi tersedia pada halaman enumeration
dan static_cast
di cppreference. Selanjutnya kita membahas tipe komposit terakhir yang paling sering disalahpahami: union, satu alamat memori yang dipakai bergantian oleh beberapa member.