Haftalık C++ 18 – std::any

close-up photography of white, pink, and red petaled flower centerpiece

Evet arkadaşlar, kurban bayramını fırsat bilerek araya bir yazı daha sıkıştırabildim 🙂 Uzun süredir yazmak istediğim ve std::optional, std::variant ve std::any kabiliyetlerinden sonuncusu olan std::any yazısı ile sizlerle birlikteyim. Bu yazı ile birlikte birbirine benzeyen bu üç kabiliyete de değinmiş olacağınız. Kod paylaşımlarımı takip edenleriniz, aslında bazı kodlarımda std::any kullanımlarını görmüş olduklarını umuyorum. Serinin diğer yazılarına aşağıdaki bağlantılardan ulaşabilirsiniz.

  1. std::optional
  2. std::variant
  3. std::any

Diğer yazılarda olduğu gibi, öncelikle böyle bir kabiliyete ihtiyaç nereden hasıl oldu? Bunu daha önce nasıl çözüyorduk veya çözebiliyor muyduk? Sonrasında ise madde madde örnekleri ile ilgili kabiliyete ilişkin kullanımları sizlere aktarıyor olacağım.

std::any:

Evet gelelim bu kabiliyetin özüne. Aslında ilgili anahtar kelimenin size bir fikir verdiğini düşünüyorum. Daha önce std::variant (ve dahi union’lar) ile birlikte ön tanımlı tipler için, belirli ve sabit bir bellek alanına, farklı zamanlarda, farklı veri tiplerinde verileri girebilmeyi sağlayan yapı mevcut idi. Bu kullanımda, olası tiplerin biliniyor olması en önemli husus. Eğer bunlar biliniyor ve sabit ise std::variant ile birlikte std::visitor doğru bir tercih olacaktır. Eğer, olası tipler belli değil veya daha sonradan farklı tiplerin de kullanılması durumu var ise o zaman std::any sizin için daha uygun bir tercih olacaktır. Peki tek fark bu mu? Hayır, bunun ile birlikte std::variant‘ta herhangi bir dinamik bellek kullanımı yoktur ve bellek alanı sabittir, fakat std::any için dinamik bellek kullanılabilir ve sonuç olarak ilkine göre biraz daha maliyetli olacaktır. Sizlere hızlı bir giriş yaptıktan sonra şimdi kabiliyete biraz daha yakından bakalım.

Yukarıda yazdıklarımdan sonra, ee ben bunu “void*” ile de yaparım. Evet aslında haklısınız, C++ 17’e kadar aşağıdaki gibi bir kullanım ile std::any‘e benzer, tip güvenliğine nispeten sahip bir kullanım elde edebilirsiniz:

fakat bu pek de güzel ve standart bir kullanım değil. Bunun yerine STL’in sunduğu kabiliyeti kullanmak daha iyi olacaktır. std::any nesneleri herhangi bir tipe ait değeri tutabilirken, aynı zamanda, tuttuğu bu değere ilişkin tip bilgisine de sahip ve olası tipleri deklarasyon aşamasında belirtmeye de gerek yok. Yukarıda örnek olarak verdiğimiz kod gibi, ilgili nesne hem atanan değeri hem de ilgili değere ilişkin tip bilgisini tutmakta. Burada ilgili kütüphane, basit tipler için dinamik bellek kullanmadan bir çözüm sunabilir ama diğer tipler için dinamik bellek alımı kaçınılmazdır.

Burada, şunu da unutmamak lazım. Her şey için de tutup std::any kullanmak tahmin edebileceğiniz üzere bir çok açıdan pek doğru olmayacaktır. Hatta mümkün olduğunca bundan kaçınmak daha doğru bir davranış olacaktır. Bu konuda daha fazla bilgi edinmek isteyenler aşağıdaki tartışmalara göz atabilirler:

https://www.reddit.com/r/cpp/comments/7l3i19/why_was_stdany_added_to_c17/

Peki hangi durumlar için std::any kullanabiliriz. Aşağıdaki durumlarda kullanılabileceği değerlendirilmektedir:

  • Öznitelikler. Özellikle grafiksel kullanıcı arayüz ya da benzeri kontroller için öznitelik verilerinin tutulması amacı ile. Ya da jenerik (isim-değer) özellik tabloları için de kullanılabilir. Buna benzer bir kullanıma, BÇOM’da görebilirsiniz,
  • Eğer farklı tiplerin olması beklenen konfigürasyon veya benzeri dosyaların ayıklanması amacı ile. Bu amaçla std::variant da kullanılabilir ama std::any ile daha jenerik bir çözüm elde edilebilir,
  • Farklı parametrelerin geçirilmesi amacı ile,
  • Betik dilleri ile entegrasyon ya da yorumlayıcıları ile,

kullanılabilir. Evet artık std::any yeteneklerine daha yakından bakabiliriz.

Kabiliyetler:

std::any kabiliyeti ilk olarak C++ 17 ile sunulmaya başlandı (tabi daha öncesinde boost kütüphanelerinde benzer bir kabiliyet sunulmaktaydı) ve bu kabiliyeti kullanmak için <any> başlık dosyasını eklemeniz ve std alan uzayına erişmeniz gerekmektedir. std::any, variant ya da optional gibi template bir sınıf değil, fakat std::optional gibi varsayılan olarak herhangi bir değer içermez (bunu has_value() API’si ile kontrol edebiliriz). Eğer bir değer ile ilklendirirseniz, ilgili nesneye o tip atanır. Şimdi diğer yeteneklere madde madde bakalım:

  • std::any nesnelerini nasıl oluşturabiliriz?
    • Başta da bahsettiğim gibi, farklı şekillerde bu tipin nesnelerini oluşturabilir, oluşturduğunuz nesneler zaman içerisinde farklı tipler tutabilir:
    • Peki std::any nesnesinin verdiğiniz değerden farklı bir tip bilgisini tutmasını istediğinizde ne yapabilirsiniz. O noktada da std::in_place_type tipini aşağıdaki gibi kullanabilirsiniz:
    • Nesnelerin kendilerini oluşturmadan (yani nesnenin kendisi oluşturmadan) birden fazla parametre alan nesneler ile std::any oluşturmak için de std::in_place_type kullanılabilir:
    •  Yukarıda verilen oluşturma yöntemleri yanında akıllı işaretçilerdeki gibi std::make_any<>() fonksiyonu da kullanılabilir.
    • Yukarıdaki oluşturma yöntemlerinden sonra std::any‘nin nesnelerin yaşam sürelerini nasıl yönettiğini merak etmiş olmalısınız. Herhangi bir bellek sızıntısına mahal vermemek için, yeni bir nesne ataması yapıldığı anda std::any nesnesi tarafından tutulan önceki nesne yok edilir,
  • Mevcut std::any nesnelerinin hangi tipte veri tuttuğunu görmek için type() API’sini kullanabiliriz,
  • Mevcut std::any nesnelerinin değerlerini nasıl değiştirebiliriz?
    • Öncelikli olarak atama operatörünü (=) kullanabilirsiniz, bunun ile birlikte emplace() API’si de bu amaçla kullanılabilir.
  • std:.any nesneleri tarafından tutulan değerlere nasıl erişebiliriz?
    • İlgili değerlere erişmek için std::any_cast bağımsız fonksiyonları kullanılabilir
    • Yukarıdaki kullanımlarda eğer tip farklılığı oluşur ise std::bad_any_cast hatası/istisnası fırlatılır.
      Eğer hatalı bir durumda hata fırlatılması yerine nullptr dönülmesini istiyor iseniz ilgili nesnenin referansı geçirilir. Yalnız işaretçisi geçirilen std::any nesnelerinin referanslarına erişim ise hataya sebep olur.
    • Bu arada başta da değinmiştim ama bütünlük açısında burada da değineyim. İlgili std::any nesnesinin herhangi bir değer içerip içermediğini ise has_value() API’si ile kontrol edebilirsiniz
  •  std::any taşıma operatörleri ile de kullanılabilmektedir. Fakat bu kabiliyeti kullanabilmek için ilgili tipin kopyalanabilir bir tip olması gerekmektedir. Yalnız taşınabilir tipler std::any ile kullanılamazlar.
  • std::any diğer benzeri yapılardan farklı olarak bellekten dinamik olarak yer alırlar. Bu konuda standart, kütüphane geliştiricileri “SBO-Small Buffer Optimization”‘ı kullanmalarını, yani int ve benzeri basit tipler için dinamik bellek kullanımından kaçınmalarını teşvik eder. Internette gördüğüm kadarıyla farklı derleyiciler kullanılarak yapılan boyut ölçümleri aşağıdaki gibi sonuç vermiş. Bu da gösteriyor ki, ciddi manada bir bellek kullanımı getiriyor.
    • Derleyicisizeof(any) sonucu
      GCC 8.1 (Coliru Ç.İ.D.)16
      Clang 7.0.0 (Wandbox Ç.İ.D.)32
      MSVC 2017 15.7.0 32-bit40
      MSVC 2017 15.7.0 64-bit64
  • std::any’i STL konteynerleri ile de rahatlıkla kullanabilirsiniz.

Sonuç olarak, bu yazı ile tamamladığımız, üç kabiliyeti, üç cümle ile özetleyecek olursak:

  • std::optional: var olan bir nesneyi tutabilen veya hiç bir şey tutmayan/tutmadığını ifade eden yapıdır,
  • std::variant: tip güvenli birlik (union) kabiliyetidir,
  • std::any: herhangi bir nesneyi tip güvenli bir şekilde tutabilmek için sunulan mekanizma.

Evet bir haftalık C++ yazımızın daha sonuna geldik dostlar, bir sonraki yazımda görüşmek dileğiyle.

Kaynaklar

Bir cevap yazın

E-posta hesabınız yayımlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir

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