12 Kasım 2018 Pazartesi

std::optional Sınıfı - C++17 İle Geliyor

Giriş
Bu sınıf C++17 ile standarda girdi. Daha önceki derleyicilerde kullanmak istersek şu satırı dahil ederiz.
#include <experitmental/optional>
Bu sınıfı kullanırken std::nullopt'ye de ihtiyaç oluyor.

C++14 - Tanımlama
Açıklaması şöyle.
Class template optional imposes little requirements on T: it has to be either an lvalue reference type, or a complete object type satisfying the requirements of Destructible.
Örnek - template
Yani T tipi incomplete olduğu için template içinde kullanılamaz. Şöyle yapamayız.
template <typename T>
struct node {
  std::experimental::optional<node<T>> next;
  T data;
};
Sebebi ise optional içinde T tipinin büyüklüğü kadar bellek alanının ayrılması. Kodu şuna benzer. Eğer T tipi incomplete ise bu kod derlenmez.
template <class T>
struct optional
{
  bool _containsValue;
  char _buffer[ sizeof( T ) ];
};
Ya da kodu şuna benzer. Neticede T tipi için yeterli bellek alanının ayrılması gerekir.
template <typename T>
struct optional
{
    std::aligned_storage_t<sizeof(T), alignof(T)> _data;
    bool _set;
};
Örnek - abstract class
Ku kod derlenmez. Sebebi yine aynı şekilde Type tipi abstract olduğu için büyüklüğünün bilinmemesi.
struct Type {
    virtual bool func(const std::string& val) const noexcept = 0;
}

// in main
optional<Type> = some_function_returning_optional_type();
C++17 - Tanımlama
C++14 ile gelen incomplete type kuralı geçerli değil. Şöyle yaparız.
template<typename T>
auto test()
{
  auto opt = std::optional<T>{T{}};
  ...
}

test<int>();
test<double>();
test<std::string>();
Alignment
Açıklaması şöyle. Yani std::optional T tipinin alignment kurallarına uymalı.
23.6.3 Class template optional
(...) The contained value shall be allocated in a region of the optional storage suitably aligned for the type T.
Constructor
İçinde nesne olmayan optional yaratır.
Örnek
Şöyle yaparız.
std::optional<T> t; // initially empty
Örnek
Şöyle yaparız
optional<Big> ob{emplace, "1"}; // calls Big{"1"} in place (no moving)
optional<Big> oc{emplace};      // calls Big{} in place (no moving)
optional<Big> od{};             // creates a disengaged optional
Constructor - std::in_place + Args
İmzası şöyle
template< class... Args > 
explicit optional( std::in_place_t, Args&&... args );
in_place construction için kullanılır. Şöyle yaparız.
std::optional<std::vector<int>> x{std::in_place, {1,2,3,4,5}}; // OK
Şu kod derlenmiyor ancak ne yapmak lazım bilmiyorum.
std::optional<std::vector<int const>> y{std::in_place, {1,2,3,4,5}}; // error!
Copy Constructor
Açıklaması şöyle
...shall be defined as deleted unless is_copy_constructible_v<T> is true.
Move Constructor
Açıklaması şöyle
...shall not participate in overload resolution unless is_move_constructible_v<T> is true.
Örnek
Şöyle yaparız.
std::optional<X> o1;
std::optional<X> o2(std::move(o1));
Örnek
Move işlemi sonunda primitive tip varsa foo halen dolu görünebilir. Şöyle yaparız.
std::optional<int> foo{ 0 };
std::optional<int> bar{ std::move(foo) };

std::cout << std::boolalpha
          << foo.has_value() << '\n'  // true
          << bar.has_value() << '\n'; // true
emplace metodu
Örnek
Şöyle yaparız.
std::optional<int> x;
x.emplace(3);
Örnek
Şöyle yaparız.
// now we're ready to create the T value
t.emplace(foo, bar); // constructs the T with foo, bar as args
operator-> metodu
Bu operator kullanılmadan önce std::optional nesnesinin dolu olması gerekir.
Örnek
Şu kod tanımsız davranışa (UB) sebep olur
std::optional<std::string> so;
std::cout << so->size() << std::endl;
std::cout << so.has_value();
operator* metodu
Örnek
İçinde nesne olmayan optional sınıfına erişmek tanımsız (undefined) davranıştır. Exception fırlatmaz. Şu kod hatalı.
optional<int> t{}; // nullopt (empty) by default
cout << *t << endl;
Örnek
Adresin hep aynı olup olmaması konusu belirsiz. Şu kod undefined behavior'a sebep olabilir.
template<typename T>
auto test()
{
  auto opt = std::optional<T>{T{}};
  auto* ptr = &*opt;

  opt.reset();
  opt = T{};

  assert(ptr == &*opt); // Can this assert fail?
}
value_or metodu
Örnek
Şöyle yaparız.
template<typename T, typename F>
T lazy_value_or(const std::optional<T> &opt, F fn) {
    if(opt) return opt.value();
    return fn();
}
Örnek
Şöyle yaparız.
template <typename F>
struct Lazy
{
  F f;  

  operator decltype(f())() const
  {
    return f();
  }
};

template <typename F>
Lazy(F f) -> Lazy<F>;

int main()
{
  std::optional<int> o;

  int i = o.value_or(Lazy{[]{return 0;}});
}

Diğer seçenekler
Eğer elimizde std::optional yoksa, ve boost::optional kullanmak istemiyorsak şöyle yapabiliriz.

1. unique_ptr kullanabiliriz.
Şöyle yaparız.
std::unique_ptr<double> possiblyFailingCalculation();
std::unique_ptr kendi içine null kontrolü yaptığı için kolaylıkla kullanılabilir.

2. std::pair kullanılabilir.
Örnek
Şöyle yaparız.
std::pair<double,bool> possiblyFailingCalculation();
Örnek
Şöyle yaparız.
using std_optional_int = std::optional<int>;
using my_optional_int = std::pair<int, bool>;

3. Eski usul kodlarız.
Yani şöyle kodlayacağımıza
std::optional<double> possiblyFailingCalculation()
Şöyle yaparız
bool possiblyFailingCalculation(double& output);

Hiç yorum yok:

Yorum Gönder