← Kursa Dön
📄 Text · 12 min

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::optional kullan. 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::monostate kullanabilirsin: ``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ı

Özellikoptionalvariantany
AmaçDeğer var/yokBirden fazla tipten biriHerhangi bir tip
Tip sayısı1Derleme zamanında bilinen NSınırsız
Tip güvenliğiDerleme zamanıDerleme zamanıÇalışma zamanı
Boş olabilirEvet (nullopt)Hayır (monostate hariç)Evet
PerformansÇok iyiİyiOrta (heap allocation olabilir)
Kullanım sıklığıÇok sıkSıkNadir
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 variant

Gerç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::visit ile 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::visit ile 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.