[:tr]Haftalık C++ 14 – std::optional[:en]Weekly C++ 14 – std::optional[:]

[:tr]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/[:en]Hello, my friends, we are together with another weekly C++ post. The topic of this post is about std::optional which is introduced with C++ 17. In this post, we are going to take alook at why we need such capability an in which cases we can use that with example codes. This post will be followed by std::variant and std::any topics which are also related with this topic.

person holding light bulb

First of all, let us look at why we need such feature. I am sure, at one point in your code, you needed to state whether a given variable contains value or not (i.e. middle names or optional arguments provided via command line). Of course, there are solutions that can be used to deal with this problem. For instance, for pointers, you may use nullptr to state but it still does not give much information about whether given pointer is not allocated or does not contain any valid value, right? This is a little bit different for other types. -1 or similar values can be used for integral values, but in that case, -1 can not be used  anymore. It may also not be possible to come up with such numbers or not suitable (i.e. for floating numbers). Besides, in those cases, you usually required to perform additional checks.

std::optional, is introduced to resolve such issues. With this feature, we are able to check if a value is available or not in a more standard and expressive manner. Such types are also known as nullable typesin some references. Similarly, this can be used for returning values and passing parameters to functions which may or may not be available.

Most of the std::optional features are based on boost::optional, so if you are familiar with that library, you can easily adapt std::optional in your codes (by the way I have added an example reference for boost::optional usage). To use std::optional, you need to add #include <optional> header file. std::optional does not allocate memory from heap, in fact, it is simply a wrapper around the type provided. At most, one additional byte is allocated to specify availability.

So, afterall, in which cases we should use this feature? Good question. Let us look at a part of explanation given in boost::optional page, to see the full description you can check out this page.:

It is recommended to use optional<T> in situations where there is exactly one, clear (to all parties) reason for having no value of type T, and where the lack of value is as natural as having any regular value of T.

You can use std::optional as class member, return value or parameter that will be passed to functions. Before delving into details and other examples, let us look at a simple example code that uses std::optional for a function that convert (if possible) string to integer:

Now, let us go over individual capabilities of std::optional usage one by one with example source code snippets. Later, we will look at complete code examples:

  • At any time, an instance of optional<T> may either contain a T value or does not contain any value at all which is express with std::nullopt (type of std::nullopt_t).
  • How can we create an instance of optional<T>?
    • We can create std::optional instances that do not contain any values as follows. Both of following instances do not contain any value and no contructor is called:

    • We can create std::optional instances with given values or instances as follow. std::optional can also deduce types from provided parameters:

    • The instances that require multiple parameters, can be separately created and passed to std::optional instances which may cause temporary copy operations. The std::optional instances can directly be created with given multiple parameters (like emplace_back in STL containers) which prevent temporary copy operations. std::in_place is used for this purpose:

    • std::make_optional method can also be used for instantiation of std::optional objects like in smart pointers. In this case, std::in_place is not required:

    • std::optional can also be used with STL containers:

  • How can we access the contained values?
    • value() API can be used to access the value contained in std::optional instance. If given instance does not contain any value, then std::bad_optional_access exception is thrown,
    • In addition to value() API, pointer operators such as * and -> can also be used with std::optional instances. Contrary to previous usage, if instance does not contain any value, then no exception is thrown and behavior is undefined. So you have to be careful 🙂

    • Lastly, value_or(defaultValue) API is also provided with optional library which returns defaultValue if given std::optional instance does not contain any value.

  • How can we check if given std::optional instance contain any value?
    • has_value() API can be used to query if given instance contain any value,
    • operator bool() is also defined and can be used with conditional statements such as if-else if,
    • Example usages are provided below:

  • How can we update existing value?
    • Assignment operator (=) ve emplace() API can be used for this purpose:

    • * operator can also be used for assignment, but the given std::optional instance should contain a value:

    • std::optional<> also support move semantics. In other words, std::move can be used to transfer instances from other instances.
  • Can we use comparison operators?
    • You can use standard comparison operators with std::optional instances.

    • If both of instances do no contain any value, == comparison returns true. If one instance does not contain any value, then different results can be observed such that no value instance always considered less than other instance that contain value. Let us look at example usages:

  • How std::optional can be used for function returns is given in the sample code that is provided at the beginning of my post.
  • reset() API can be used to reset/clear the value contained in given instance.

It is time to look at two complete code examples which illustrate basic std::optional usages. I took first example from the book that I gave in references section:

The other example code is taken from CPP reference page. I hope these two example codes help you to understand std::optional better. 

See you in my next post. Have plenty of coding 🙂

References:

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/[:]

4 Comments [:tr]Haftalık C++ 14 – std::optional[:en]Weekly C++ 14 – std::optional[:]

    1. yazılımperver

      Güzel geri bildiriminiz için teşekkür ediyorum, faydalı olduysa ne ala.

Comments are closed.