std::optional, std::variant, std::any
C++17, değer yönetimini daha güvenli ve ifade edici hale getiren üç yeni tip getirdi: optional, variant ve any. Bu tipler, "değer olmayabilir", "birden fazla tipten biri olabilir" ve "herhangi bir tip olabilir" durumlarını ele alır.
Bir kutu analojisi düşün: optional ya dolu ya boş bir kutu. variant etiketli kutulardan biri — her zaman bir kutu dolu, ama hangisi olduğunu bilirsin. any ise sihirli bir kutu — içine ne koyarsan koy, ama açarken ne olduğunu bilmen lazım.
std::optional — Değer Olabilir veya Olmayabilir
std::optional<T>, ya bir T değeri tutar ya da hiçbir şey tutmaz. "Değer yok" durumunu ifade etmenin tip güvenli yoludur. Null pointer kullanmak yerine optional kullan.
Neden Gerekli?
// Problem: Kullanıcı bulunamadığında ne döndürelim?
// int findAge(std::string name);
// Eski yol 1: -1 gibi sihirli değer → hataya açık
// Eski yol 2: bool döndür + referans parametre → çirkin
// Eski yol 3: pointer döndür + nullptr → tehlikeli
// Modern yol: optional
// std::optional<int> findAge(std::string name);Temel Kullanım
#include <optional>
#include <string>
#include <iostream>
std::optional<std::string> findUser(int id) {
if (id == 1) return "Ali";
if (id == 2) return "Ayse";
return std::nullopt; // Değer yok
}
int main() {
auto user1 = findUser(1);
auto user3 = findUser(3);
// has_value() kontrolü
if (user1.has_value()) {
std::cout << "Bulundu: " << user1.value() << "\n";
}
// Kısa yol — bool dönüşümü
if (user1) {
std::cout << "Bulundu: " << *user1 << "\n"; // * ile erişim
}
if (!user3) {
std::cout << "Kullanici 3 bulunamadi\n";
}
return 0;
}has_value(), value() ve value_or()
#include <optional>
#include <string>
#include <iostream>
std::optional<int> parseNumber(const std::string& str) {
try {
return std::stoi(str);
} catch (...) {
return std::nullopt;
}
}
int main() {
auto num1 = parseNumber("42");
auto num2 = parseNumber("abc");
// value() — değer yoksa std::bad_optional_access fırlatır
std::cout << "num1: " << num1.value() << "\n"; // 42
// value_or() — değer yoksa varsayılan döner
std::cout << "num2: " << num2.value_or(-1) << "\n"; // -1
// Güvenli erişim pattern'ı
if (auto result = parseNumber("123"); result) {
std::cout << "Sayi: " << *result << "\n";
} else {
std::cout << "Gecersiz sayi\n";
}
return 0;
}Optional ile Pratik Kullanım
#include <optional>
#include <map>
#include <string>
#include <iostream>
class Config {
std::map<std::string, std::string> data;
public:
Config() : data({
{"host", "localhost"},
{"port", "8080"}
}) {}
std::optional<std::string> get(const std::string& key) const {
if (auto it = data.find(key); it != data.end()) {
return it->second;
}
return std::nullopt;
}
};
int main() {
Config config;
// Var olan ayar
auto host = config.get("host");
std::cout << "Host: " << host.value_or("bilinmiyor") << "\n";
// Olmayan ayar
auto dbName = config.get("database");
std::cout << "DB: " << dbName.value_or("default_db") << "\n";
return 0;
}Optional Oluşturma Yolları
#include <optional>
#include <string>
#include <iostream>
int main() {
// Boş optional
std::optional<int> empty;
std::optional<int> empty2 = std::nullopt;
// Değerli optional
std::optional<int> full = 42;
std::optional<std::string> name = "Ali";
// make_optional
auto opt = std::make_optional<std::string>("Merhaba");
// emplace — yerinde oluşturma
std::optional<std::string> lazy;
lazy.emplace("Sonradan olusturuldu");
// Sıfırlama
full.reset(); // Artık boş
std::cout << "Full has value: " << full.has_value() << "\n"; // false
return 0;
}💡 Null pointer alternatifi olarak optional: Pointer döndüren fonksiyonlarda "bulunamadı" durumunu nullptr ile ifade etmek yerine,
std::optionalkullan. Pointer'a göre avantajları: ownership belirsizliği yok, null dereference riski yok, kopya semantiği açık.
std::variant — Type-Safe Union
std::variant, birden fazla tipten tam olarak birini tutan bir yapıdır. C'deki union'ın tip güvenli, modern versiyonu.
Temel Kullanım
#include <variant>
#include <string>
#include <iostream>
int main() {
// int VEYA double VEYA string tutabilir
std::variant<int, double, std::string> value;
value = 42;
std::cout << "int: " << std::get<int>(value) << "\n";
value = 3.14;
std::cout << "double: " << std::get<double>(value) << "\n";
value = "Merhaba";
std::cout << "string: " << std::get<std::string>(value) << "\n";
// İndeks ile erişim
value = 42;
std::cout << "index 0: " << std::get<0>(value) << "\n";
// Hangi tip aktif?
std::cout << "Aktif index: " << value.index() << "\n"; // 0 (int)
return 0;
}Güvenli Erişim
#include <variant>
#include <string>
#include <iostream>
int main() {
std::variant<int, std::string> data = "Ali";
// YANLIŞ tip ile erişim — exception fırlatır
try {
int x = std::get<int>(data); // std::bad_variant_access!
} catch (const std::bad_variant_access& e) {
std::cout << "Hata: " << e.what() << "\n";
}
// Güvenli erişim — get_if (pointer döner)
if (auto ptr = std::get_if<std::string>(&data)) {
std::cout << "String: " << *ptr << "\n";
} else {
std::cout << "String degil\n";
}
// holds_alternative kontrolü
if (std::holds_alternative<std::string>(data)) {
std::cout << "String tutuyor: " << std::get<std::string>(data) << "\n";
}
return 0;
}std::visit ile Variant Kullanımı
std::visit, variant'taki aktif tipe göre uygun fonksiyonu çağırır. Visitor pattern'ının modern uygulamasıdır.
#include <variant>
#include <string>
#include <iostream>
int main() {
using Value = std::variant<int, double, std::string>;
Value v1 = 42;
Value v2 = 3.14;
Value v3 = std::string("Merhaba");
// Lambda ile visit
auto printer = [](const auto& val) {
std::cout << val << "\n";
};
std::visit(printer, v1); // 42
std::visit(printer, v2); // 3.14
std::visit(printer, v3); // Merhaba
return 0;
}Overloaded Visitor (İleri Teknik)
Her tip için farklı davranış istiyorsan, overloaded lambda pattern'ını kullanabilirsin:
#include <variant>
#include <string>
#include <iostream>
// Overload helper — C++17
template<class... Ts> struct overloaded : Ts... { using Ts::operator()...; };
template<class... Ts> overloaded(Ts...) -> overloaded<Ts...>;
int main() {
using Shape = std::variant<int, double, std::string>;
Shape shape = 3.14;
std::visit(overloaded{
[](int radius) {
std::cout << "Daire, yaricap: " << radius << "\n";
},
[](double area) {
std::cout << "Alan: " << area << "\n";
},
[](const std::string& name) {
std::cout << "Sekil: " << name << "\n";
}
}, shape);
return 0;
}Variant ile Hata Yönetimi
#include <variant>
#include <string>
#include <iostream>
struct Error {
int code;
std::string message;
};
// Result tipi — ya değer ya hata
using Result = std::variant<int, Error>;
Result divide(int a, int b) {
if (b == 0) {
return Error{-1, "Sifira bolme hatasi"};
}
return a / b;
}
int main() {
auto result1 = divide(10, 3);
auto result2 = divide(10, 0);
auto handle = [](const Result& r) {
if (std::holds_alternative<int>(r)) {
std::cout << "Sonuc: " << std::get<int>(r) << "\n";
} else {
auto& err = std::get<Error>(r);
std::cout << "Hata [" << err.code << "]: " << err.message << "\n";
}
};
handle(result1); // Sonuc: 3
handle(result2); // Hata [-1]: Sifira bolme hatasi
return 0;
}⚠️ variant hiçbir zaman "boş" olamaz — her zaman bir tipte değer tutar. İlk tip varsayılan olarak oluşturulur. Eğer ilk tip default-constructible değilse,
std::monostatekullanabilirsin: ``cpp std::variant<std::monostate, NonDefault> v; // monostate ile "boş" variant``
std::any — Herhangi Bir Tip
std::any, herhangi bir tipte değer tutabilir. Tip bilgisi çalışma zamanında kontrol edilir. void*'ın tip güvenli versiyonu gibi düşünebilirsin.
#include <any>
#include <string>
#include <iostream>
int main() {
std::any value;
value = 42;
std::cout << "int: " << std::any_cast<int>(value) << "\n";
value = std::string("Merhaba");
std::cout << "string: " << std::any_cast<std::string>(value) << "\n";
value = 3.14;
std::cout << "double: " << std::any_cast<double>(value) << "\n";
// Yanlış tip — exception
try {
int x = std::any_cast<int>(value); // value double tutuyor!
} catch (const std::bad_any_cast& e) {
std::cout << "Hata: " << e.what() << "\n";
}
// Tip kontrolü
if (value.type() == typeid(double)) {
std::cout << "double tutuyor\n";
}
// Boş mu?
std::any empty;
std::cout << "Has value: " << empty.has_value() << "\n"; // false
return 0;
}any Ne Zaman Kullanılmalı?
std::any çok esnektir ama bu esneklik bir bedelle gelir — tip güvenliği derleme zamanında değil, çalışma zamanında kontrol edilir.
#include <any>
#include <map>
#include <string>
#include <iostream>
#include <vector>
int main() {
// Kullanım alanı: heterojen property sistemi
std::map<std::string, std::any> properties;
properties["name"] = std::string("Ali");
properties["age"] = 25;
properties["score"] = 3.75;
properties["active"] = true;
// Erişim — tipini bilmen lazım
auto name = std::any_cast<std::string>(properties["name"]);
auto age = std::any_cast<int>(properties["age"]);
std::cout << name << " (" << age << ")\n";
return 0;
}any kullanma kuralı: Eğer olası tipleri biliyorsan `variant` kullan. any'yi sadece gerçekten hangi tiplerin geleceğini bilmediğin durumlarda (plugin sistemi, generic property store gibi) tercih et.
Üç Tipin Karşılaştırması
| Özellik | optional | variant | any |
|---|---|---|---|
| Amaç | Değer var/yok | Birden fazla tipten biri | Herhangi bir tip |
| Tip sayısı | 1 | Derleme zamanında bilinen N | Sınırsız |
| Tip güvenliği | Derleme zamanı | Derleme zamanı | Çalışma zamanı |
| Boş olabilir | Evet (nullopt) | Hayır (monostate hariç) | Evet |
| Performans | Çok iyi | İyi | Orta (heap allocation olabilir) |
| Kullanım sıklığı | Çok sık | Sık | Nadir |
Ne zaman hangisi?
│
├── Değer olmayabilir mi?
│ └── Evet → std::optional ✅
│
├── Olası tipler belli mi?
│ └── Evet → std::variant ✅
│
├── Tamamen bilinmeyen tipler mi?
│ └── Evet → std::any (son çare)
│
└── Emin değilsen → optional veya variantGerçek Dünya Örneği: JSON-benzeri Veri Yapısı
#include <variant>
#include <string>
#include <vector>
#include <map>
#include <iostream>
#include <optional>
// JSON benzeri değer tipi
struct JsonValue;
using JsonObject = std::map<std::string, JsonValue>;
using JsonArray = std::vector<JsonValue>;
struct JsonValue {
std::variant<
std::nullptr_t, // null
bool, // true/false
int, // sayı
double, // ondalıklı sayı
std::string // metin
> data;
};
// Güvenli erişim fonksiyonu
std::optional<std::string> getString(const JsonValue& jv) {
if (auto ptr = std::get_if<std::string>(&jv.data)) {
return *ptr;
}
return std::nullopt;
}
std::optional<int> getInt(const JsonValue& jv) {
if (auto ptr = std::get_if<int>(&jv.data)) {
return *ptr;
}
return std::nullopt;
}
int main() {
JsonValue name;
name.data = std::string("Ali");
JsonValue age;
age.data = 25;
JsonValue nothing;
nothing.data = nullptr;
// Güvenli erişim
std::cout << "Name: " << getString(name).value_or("?") << "\n";
std::cout << "Age: " << getInt(age).value_or(0) << "\n";
std::cout << "Nothing as string: " << getString(nothing).value_or("null") << "\n";
return 0;
}Özet
`std::optional<T>` ya bir değer tutar ya da boştur (
std::nullopt). Null pointer'a veya sihirli değerlere (-1 gibi) güvenli bir alternatiftir.value_or()ile varsayılan değer sağlayabilirsin.`std::variant<T1, T2, ...>` derleme zamanında bilinen tiplerden tam olarak birini tutar. C union'ının tip güvenli versiyonudur.
std::visitile aktif tipe göre işlem yapılır.`std::any` herhangi bir tipte değer tutabilir ama tip kontrolü çalışma zamanında yapılır. Performansı düşük, kullanımı risklidir — sadece gerçekten gerektiğinde kullan.
Tercih sırası: optional > variant > any. Olası tipleri biliyorsan variant, değer yok durumu varsa optional, hiçbir şey bilmiyorsan any.
std::visitile overloaded lambda pattern'ı, variant içindeki her tipe farklı davranış tanımlamak için kullanılır.optional, modern C++'ta fonksiyonlardan "başarısız olabilecek" dönüşler için standart yoldur — pointer döndürmek yerine optional tercih et.
AI Asistan
Sorularını yanıtlamaya hazır