C++17 - std::optional

Herhangi bir türü nullable hale getirmek için kullanılır. Nullable bir türe örnek vermek gerekirse pointerlar, doğası gereği Nullable türlerdir. Yani,

// p  int * türünden bir pointer olsun
if (p != nullptr) {

// do something
}

int * func (bool cond)
{
if (cond) {
return p;
}
else {
return nullptr;
}

}

nullptr atanmış bir pointer programcıya değer taşımadığını bildirir. Örneklerde de görüldüğü üzere Nullable türler aslında bellekte atanmış bir değer olup olmadığını bize bildiren türlerdir. Kullanıcı tanımlı türlerde ise benzer şekilde bir bool nesnesi eklenerek bu şekilde bir fonksiyon kazandırılabilir. Structured Bindings konusunda da değindiğimiz üzere std::pair veya std::tuple döndürülerek elde edilen işlevsellik std::optional ile daha okunabilir bir şekilde elde edilebilir.

Örneğin aşağıdaki kod örneğini inceleyelim;


std::string getDomain(const std::string &email)
{
if (size_t idx = email.find('@'); idx != std::string::npos)
return std::string(email.begin() + idx + 1, email.end());
else
return "";
}

Yukarıdaki örneğe baktığımızda aslında oluşan hata durumunda null gibi bir yapı dönülmesi gerekirken boş string nesnesi bu görevi yapmak üzere seçilmiş.
std::optional kullanarak yukarıdaki fonksiyon yazılsaydı aşağıdaki gibi bir kod ortaya çıkacaktı;


std::optional<std::string> getDomain(const std::string &email)
{
std::optional<std::string> ret = std::nullopt;

if (size_t idx = email.find('@'); idx != std::string::npos){
ret = std::string(email.begin() + idx + 1, email.end());
return ret;
}

else
return ret;
}

Daha yakışıklı olmadı mı ? :)) Ek olarak C++17 ile beraber gelen standart copy elisiondan da faydalanırsak yukarıdaki kodu daha iyi hale şu şekilde
getirebiliriz;

std::optional<std::string> getDomain(const std::string &email)
{

if (size_t idx = email.find('@'); idx != std::string::npos){
return std::string(email.begin() + idx + 1, email.end());;
}

else
return std::nullopt;
}

Artık return değerinin kopyalama maliyetinden de kurtulmuş olduk.

in_place

std::optional oluşturulurken tıpkı stldeki bazı containerlarda bulunan emplace’e benzer bir kullanımda sunuluyor. Bunun avantajı move veya copy operasyonları yasaklanan nesneleri kullanmamıza olanak sağlıyor. Diğer yandan std::vector gibi containerlarda da kolayca initialize işlemini yapmamıza olanak sağlıyor.

std::optional<std::atomic_int> a_int(std::in_place, 5);
std::optional<std::vector<int>> a_ivec(std::in_place, {1, 3, 4, 5, 6});

if (a_ivec) {
for (auto &el : a_ivec.value())
std::cout << el << std::endl;
}

make_optional()

akıllı zeki göstericilerden hatırlayacağınız üzere make_(type) şeklinde factory fonksiyonlarımız std::optional için de benzer kolaylık sağlamakta.

auto complex_point = std::make_optional<std::complex<double>>(3.0, 4.0);
auto my_email = std::make_optional<std::string>("admin@cppturkey.com");

Temel olarak std::optional bu işleri yapıyor tek tek üye fonksiyonların üzerinden geçme niyetinde değilim orası size kalmış artık :). Son olarak zaten nullable type olan göstericileri(pointers) std::optional ile kullanmak işleri kompleks hale getirebilir, dikkat. Dipnot; std::optional da bir çok diğer özellik gibi boost::optional’da elde edilen tecrübe ile tasarlanmış ve standartlara eklenmiştir :).