Haftalık C++ 14 – std::optional

Merhabalar arkadaşlar, yeni bir haftalık C++ yazımız ile birlikteyiz. Bu yazımın konusu, C++ 17 ile birlikte dile dahil edilen std::optional yeteneği. Bu kabiliyete neden ihtiyacımız var, nerelerde kullanabiliriz gibi sorulara çeşitli kod örnekleri üzerinden giderek bakacağız. Bu yapı ile ilintili olarak std::variant ve std::any yapılarına da farklı yazılarımda değineceğim. O zaman hemen başlayalım ne dersiniz.

person holding light bulb

Öncelikli olarak neden böyle bir yapıya ihtiyacımız var ona bakalım. Bazı durumlarda, değişkenlerde anlamlı bir veri olmadığını ifade etmek istediğiniz emin olmuştur (komut satırından girilen opsiyonel argümanlar ya da girilse de girilmese olabilecek alanlar, göbek ismi gibi). Mevcut durumda, bunu yapmanın elbette bir takım yolları var. Bunu işaretçilerde yapmak nispeten kolay, nullptr ile yapabilirsiniz ama bu ilklendirilmediği anlamına mı gelir ya da anlamlı veri olmadığına mı? Diğer tipler için durum biraz daha farklı. Tamsayı değişkenler için -1 ve benzeri değerler kullanabilirsiniz, bu durumda da artık -1 kullanamazsınız. Bazı durumlarda -1 gibi bir sayı bulmanız da zor olabilir ya da her seferinde bu amaç için kullanabileceğiniz bir rakam bulmak zor olabilir. Ör. Özel yapılar ya da ondalık sayılarda ne kullanacağız? Ayrıca bu tarz kullanımlarda, fazladan veri var mı ya da anlamlı mı, kontrolleri yapmanız gerekebilir (ör. önce ilgili değişken geçerli mi değil mi kontrolü, sonrasında eğer geçerli ise değişken değerini almak gibi).

İşte std::optional, tam da bu noktada imdadımıza yetişiyor. Bu yapı ile birlikte, ilgili değişkenin olup olmadığını/herhangi bir değer içerip içermediğini daha anlamlı ve standart bir şekilde gerçekleştirebileceğiz. Bu kabiliyet çeşitli kaynaklarda “nullable types” olarak da geçmektedir. Giriş kısmında anlattıklarımızı özetleyecek olursa, hiçbir değer almayacak tipleri ifade etmek için, bir işlem sonucunda anlamlı bir dönüş olmayacağı durumlarda ya da fonksiyonlara bir değer geçirme ya da geçirmeme durumları söz konusu olduğunda kullanılabilir.

std::optional, diğer bir takım kabiliyet gibi, boost::optional’ı baz almakta, o sebeple eğer onu kullandıysanız, geçişiniz çok zor olmayacaktır (bu arada boost kullanımına ilişkin örnek bir kaynak ekledim). İlgili kabiliyeti kullanmak için #include <optional> başlık dosyasını eklemeniz yeterli. std::optional ek olarak herhangi bir dinamik bellek kullanımı yapmaz, aslında basit bir “wrapper” olarak da değerlendirilebilir. Genelde ilgili yapının kendi kaplayacağı alan yanında, fazladan sadece bir byte kadar bir alan kullanır.

Peki, hangi durumlarda bu yapıyı kullanmalıyız? Güzel bir soru. Bu anlamda kalem kalem olası kullanım alanlarından bahsetmeden önce, boost::optional sayfasında, bu konuda verilen yazıya  bir göz atabilirsiniz. İlgili sayfada daha detaylı bilgi verilse de, kısa bir özetini burada verelim:

optional<T> yi ilgili T değeri olmaması, T’ye ilişkin bir değer olması kadar normal, açık ve  geçerli bir sebebin olduğu durumlarda kullanılması tavsiye edilmektedir.

Bu yapıyı bir sınıf üyesi olarak, dönüş değeri olarak ya da fonksiyona geçirilecek olan bir parametre olarak kullanabilirsiniz.

Detaylara geçmeden önce hemen bir örneğe bakalım. Bu örnek kod içerisinde string’i int’e çeviren bir metoda bakacağız:

Şimdi kalem kalem std::optional kullanımına dair hususların üzerinden, örnek kodlar ile birlikte geçelim isterseniz. Daha sonra bu kabiliyetleri içeren daha kapsamlı kodlara da bakarız:

  • Herhangi bir zamanda optional<T> nesnesi ya bir değer içerir ya da herhangi bir değer içermez ki bu da std::nullopt ile ifade edilir ki bu da aslında std::nullopt_t tipindedir.
  • Nasıl oluşturabiliriz?
    • Herhangi bir değer içermeyen nesneleri aşağıdaki gibi oluşturabiliriz. Aşağıdaki iki kullanımda bir değer içermez ve yapıcı çağrılmaz

    • Bir değer veya farklı bir nesne ile oluşturmak için de aşağıdaki kullanımlar geçerlidir. Burada ayrıca tiplerden bahsetmeye gerek yok, std::optional bu çıkarımları yapabilir:

    • Birden fazla parametre alan nesneler için ilgili nesneyi oluşturup geçirebilirsiniz. Ya da daha güzeli direk ilgili nesne ile birlikte oluşturabilirsiniz, bu sayede geçici bir nesne oluşturulmasından da kurtulursunuz. Bunun için de std::in_place yapısı kullanılmakta:

    • Akıllı işaretçilerdeki gibi std::make_optional metodu da sunulmaktadır. Bu durumda, std::in_place kullanmanıza gerek yoktur:

    • Yine konteynerler ile de kullanılabilir:

  • Değere nasıl ulaşacağız?
    • İlgili nesne içerisindeki değere ise value() ile ulaşabilirsiniz. Eğer ilgili nesne herhangi bir değer içermiyor ise o zaman std::bad_optional_access istisnası fırlatılır,
    • Akıllı işaretçilere benzer kullanım da sunulmaktadır. * ve -> operatörler tanımlanmıştır ve bunlarda ilgili değerlere ulaşmak için kullanılabilirler. Bir önceki kullanımın aksine, eğer ilgili nesne bir değer içermiyor ise, o zaman herhangi bir istisna fırlatılmaz ve davranış tanımlı değildir. Yani dikkatli olmak lazım 🙂

    • Son olarak value_or(varsayılanDeger) kullanımı da mevcut. Yani eğer ilgili nesne herhangi bir değer içermiyor ise varsayılanDeger dönülür.

  • Değer var mı nasıl sorgulayacağız?
    • Herhangi bir std::optional nesnesinin bir veri içerip/içermediğini has_value() metodu ile sorgulayabilirsiniz.
    • Ayrıca operator bool() da tanımlı olduğu için, nesnesinin kendisini if bloğunda kontrol edebilirsiniz.
    • Aşağıda bu kullanımları görebilirsiniz:

  • Mevcut değeri nasıl değiştirebiliriz?
    • Atama (=) operatörü ve emplace() API si ile bunu gerçekleştirebiliriz:

    • Ayrıca işaretçilerdeki gibi * operatörü ile de ilgili nesnelere değer atanabilir:

    • Atama yanında std::optional<> aynı zamanda taşıma mantıklarını da desteklemektedir. Bir diğer ifade ile std::move ile farklı bir nesne, bir diğer nesneye taşınabilir.
  • Karşılaştırma operatörleri ile kullanabilir miyiz?
    • Standart karşılaştırma operatörlerini nesneler ile kullanabilirsiniz.

    • Eğer her iki nesne içerisinde de değer yok ise == true döner. Diğer karşılaştırmaların hepsi false döner. Yalnız, nesnelerden birisi eğer std::nullopt ise yani yok ise farklı durumlar ortaya çıkabilir. İçerisinde değer olmayan nesne her zaman diğerlerinden az olarak kabul edilir:

  • std::optional metot dönüş değeri olarak nasıl kullanılır? Yazının başında verdiğimiz kod parçası bu anlamda aslında güzel bir örnek.
  • reset() metodu ile de nesne içerisindeki tutulan değer atılır ve boş hale getirilir.

Şimdi çok temel bazı kullanımları içeren örnek kodlara bakalım. Bunların ilki, referanslarda verdiğim bir kitaptan aldığım kod:

Diğeri de ilgili referans sayfasından. Bu iki örnek de sizlere std::optional kullanımı hakkında sizlere fikir verecektir. 

Bir sonraki yazımda görüşmek dileğiyle. Bol kodlu günler:

Kaynaklar:

https://www.boost.org/doc/libs/1_70_0/libs/optional/doc/html/boost_optional/tutorial/when_to_use_optional.html

https://en.cppreference.com/w/cpp/utility/optional

https://arne-mertz.de/2018/06/modern-c-features-stdoptional/

C++ 17 The Complete Guide, Nicolai M. Josuttis

https://www.fluentcpp.com/2016/11/24/clearer-interfaces-with-optionalt/

Bir cevap yazın

E-posta hesabınız yayımlanmayacak.

This site uses Akismet to reduce spam. Learn how your comment data is processed.