Sebelum C++17, menandai “fungsi ini bisa gagal menghasilkan nilai” selalu memakai trik yang sama: kembalikan nilai penanda seperti -1, nullptr, atau string kosong, lalu berharap semua pemanggil ingat memeriksanya. Trik itu rapuh — lupa satu pemeriksaan, nilai penanda ikut dihitung seperti nilai sah. std::optional<T> menggantikan trik itu dengan tipe yang dinyatakan secara eksplisit: objeknya berisi nilai T atau benar-benar kosong, dan compiler memaksa kita berurusan dengan kedua kemungkinan itu. Chapter ini membandingkan gaya lama dan gaya optional secara berdampingan, lalu merangkum kapan optional menjadi pilihan yang tepat.
Sentinel vs optional: Masalah yang Dipecahkan #
Gaya sentinel memiliki cacat mendasar: nilai penanda -1 adalah int biasa, sehingga cariSentinel(teks, 'x') * 2 tetap dikompilasi tanpa keluhan — dan menghasilkan -2, angka yang sama sekali tidak bermakna. Dengan std::optional<int>, tidak ada aritmetika langsung: untuk memakai nilainya kita wajib melewati pemeriksaan (if, value_or, atau *), sehingga jalur kegagalan tidak mungkin terselip diam-diam.
#include <iostream> // std::cout
#include <optional> // std::optional, std::nullopt
#include <string> // std::string
// Cara lama: sentinel -1 berarti "tidak ditemukan"
int cariSentinel(const std::string& teks, char target) {
for (std::size_t i = 0; i < teks.size(); ++i) {
if (teks[i] == target) return static_cast<int>(i);
}
return -1; // nilai khusus penanda "gagal"
}
// Cara modern: optional<int> berarti "posisi, ATAU tidak ada"
std::optional<int> cariOptional(const std::string& teks, char target) {
for (std::size_t i = 0; i < teks.size(); ++i) {
if (teks[i] == target) return static_cast<int>(i);
}
return std::nullopt; // tanpa nilai, tanpa trik angka
}
struct Kupon {
std::string kode;
int diskon; // persen
};
// optional juga bisa menampung struct: data ATAU kegagalan
std::optional<Kupon> cariKupon(const std::string& kode) {
if (kode == "HEMAT20") return Kupon{"HEMAT20", 20};
return std::nullopt;
}
int main() {
std::string kalimat = "standard library";
// --- gaya sentinel ---
std::cout << "Sentinel 'd' : " << cariSentinel(kalimat, 'd') << "\n";
std::cout << "Sentinel 'x' : " << cariSentinel(kalimat, 'x') << "\n";
// --- gaya optional ---
std::optional<int> posisi = cariOptional(kalimat, 'd');
if (posisi.has_value()) { // bentuk ringkas: if (posisi)
std::cout << "Optional 'd' : " << *posisi << "\n";
}
std::optional<int> kosong = cariOptional(kalimat, 'x');
std::cout << "Optional 'x' ada nilai? "
<< (kosong ? "ya" : "tidak") << "\n";
// value_or: nilai cadangan bila kosong, tanpa pemeriksaan eksplisit
std::cout << "value_or 'x' : " << kosong.value_or(0) << "\n";
std::cout << "value_or 'd' : " << posisi.value_or(0) << "\n";
// --- optional berisi struct ---
std::optional<Kupon> kupon = cariKupon("HEMAT20");
if (kupon) {
std::cout << "Kupon : " << kupon->kode
<< " (-" << kupon->diskon << "%)\n";
}
std::optional<Kupon> gagal = cariKupon("SALAH123");
std::cout << "Kupon SALAH123 : "
<< (gagal.has_value() ? "ada" : "tidak ada") << "\n";
return 0;
}Output yang Dihasilkan:
Sentinel 'd' : 4
Sentinel 'x' : -1
Optional 'd' : 4
Optional 'x' ada nilai? tidak
value_or 'x' : 0
value_or 'd' : 4
Kupon : HEMAT20 (-20%)
Kupon SALAH123 : tidak adaMari kita bedah:
- Baris 10 (
return -1;): inilah gaya lama.-1bukan posisi yang sah — ia kode bermimpi menjadi pesan kegagalan, dan tidak ada yang mencegah pemanggil memperlakukannya sebagai posisi. - Baris 18 (
return std::nullopt;):std::nulloptadalah konstanta “tanpa nilai” milikoptional. Tipe kembalianstd::optional<int>sudah menceritakan kontraknya di tanda tangan fungsi: hasilnya int atau tidak ada. - Baris 41-42 (
has_valuedan*posisi): pemeriksaan keberadaan memakaihas_value(), dan bentuk ringkasnya cukupif (posisi)karena optional bisa dikonversi kebool. Operator*mengambil nilai di dalamnya — sah hanya setelah kita yakin berisi. - Baris 46-47: percobaan
'x'menghasilkan optional kosong; pencetakankosong ? "ya" : "tidak"kembali memakai konversi kebool. - Baris 50-51 (
value_or):value_or(0)berarti “ambil nilai, atau 0 bila kosong” — satu panggilan untuk pola nilai cadangan yang sehari-hari kita pakai (misalnya angka default). Perhatikan'x'menghasilkan0sedangkan'd'tetap4. - Baris 56-57 (
operator->): untuk optional berisi struct,kupon->kodemengakses anggota tanpa menulis(*kupon).kode. Semantiknya sama dengan pointer, tetapi objeknya disimpan di dalam optional, bukan di heap. - Baris 59-61: kode kupon yang tidak terdaftar menghasilkan
nullopt, dan pemanggilnya memeriksa sebelum menyentuhkodeataudiskon— pola pemakaian yang aman dari salah baca.
value(), value_or(), dan operator* #
std::optional menyediakan tiga pintu menuju isinya, masing-masing dengan semantik berbeda:
| Cara | Saat berisi | Saat kosong |
|---|---|---|
if (opt) { *opt } | mengembalikan nilai | * pada optional kosong = undefined behavior |
opt.value() | mengembalikan nilai | melempar exception std::bad_optional_access |
opt.value_or(x) | mengembalikan nilai | mengembalikan x |
Karena value() melempar exception, pembahasannya baru tuntas setelah kita mengenal mekanisme try/catch pada Section Error Handling; untuk sekarang cukup diingat: bila jalur kosong memang biasa terjadi dan punya jawaban wajar, value_or adalah pilihan paling nyaman.
optional menjawab pertanyaan “ada atau tidak”. Pertanyaan berikutnya lebih kaya: “nilai ini berupa salah satu dari beberapa tipe yang mana?” — itulah std::variant, topik Chapter berikutnya.
Rujukan resmi: cppreference — std::optional , cppreference — optional::value_or .