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.
- std::optional
- std::variant
- 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:
1 2 3 4 5 |
class OurAnyClass { void* mValue; std::type_info mTypeInfo; }; |
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:
-
1234std::any emptyAny; // boş bir nesnestd::any dblAny = 4.3; // b 4.3 değerine sahip ve tipi doubleemptyAny = 42; // artık int tipinde ve 42 değerine sahipdblAny = std::string{"merhaba"}; // dblAny std::string tipinde ve "merhaba" içeriyor
- 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:
-
12std::any a4{std::in_place_type<long>, 42};std::any a5{std::in_place_type<std::string>, "hello"};
- 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:
-
12345// Burada ilgili nesne oluşturulduğu için std::in_place_type e gerek yokstd::any complex1{std::complex{3.0, 4.0}};// Burada hangi nesne oldugunu anlaması için std::in_place_type gecirmemiz lazimstd::any complex2{std::in_place_type<std::complex<double>>, 3.0, 4.0};
- Yukarıda verilen oluşturma yöntemleri yanında akıllı işaretçilerdeki gibi std::make_any<>() fonksiyonu da kullanılabilir.
-
123auto floatInst = std::make_any<float>(3.1);auto strInst = std::make_any<std::string>("merhaba");auto objectInst = std::make_any<std::complex<double>>(3.0, 4.0);
- 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,
-
12345678910111213141516struct ExampleType{ExampleType(){ std::cout << "ExampleType yapicisi cagrildi!\n"}~ExampleType(){ std::cout << "ExampleType yikicisi cagrildi!\n"}int mData;};std::any anyInstance = std::make_any<ExampleType>();var = 100.0F;// Asagidaki satir calistirilmasi ile su ciktilari goruruz// ExampleType yapicisi cagrildi!// ExampleType yikicisi cagrildi!// 100std::cout << std::any_cast<float>(var) << "\n";
- Mevcut std::any nesnelerinin hangi tipte veri tuttuğunu görmek için type() API’sini kullanabiliriz,
-
12345678910111213141516std::vector<std::any> listOfElements;v.push_back(42);std::string s = "merhaba";v.push_back(s);for (const auto& a : v){if (a.type() == typeid(std::string)){std::cout << "string value: " << std::any_cast<const std::string&>(a) << '\n';}else if (a.type() == typeid(int)){std::cout << "int value: " << std::any_cast<int>(a) << '\n';}}
-
- 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.
-
1234std::any anyInst;anyInst = 42; // anyInst nesnesi int degerini iceriranyInst = "hello"; // anyInst nesnesi artık char* degerini iceriranyInst.emplace {std::in_place_type<std::string>, "hello"}; // anyInst nesnesi artık std::string degerini icerir
- 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
-
1234std::string anyValue("merhaba dunya");std::any_cast<std::string>(anyValue); // Tutulan degere iliskin bir kopya donulurstd::any_cast<std::string&>(anyValue); // Tutulan degere referans donulurstd::any_cast<const std::string&>(anyValue); // Tutulan degere sadece sabit referans araciligi ile erisim saglanir
- 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. -
123std::any_cast<std::string>(&a) // Tutulan degere isaretci araciligi ile yazılabilir erisimstd::any_cast<const std::string>(&a); // Tutulan degere isaretci araciligi ile sadece okunabilir erisimstd::any_cast<std::string&>(&a); // Çalışma zamanı hatası
- 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
1234if (anyInstance.has_value()){...}
- 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.
-
Derleyici sizeof(any) sonucu
GCC 8.1 (Coliru Ç.İ.D.) 16 Clang 7.0.0 (Wandbox Ç.İ.D.) 32 MSVC 2017 15.7.0 32-bit 40 MSVC 2017 15.7.0 64-bit 64
-
- std::any’i STL konteynerleri ile de rahatlıkla kullanabilirsiniz.
-
123456789101112131415161718192021222324252627// std::map ile ornek kullanimstd::map<std::string, std::any> anyDictionary;anyDictionary["integer"] = 10;anyDictionary["string"] = std::string("Merhaba yazilimperver");anyDictionary"float"] = 1.0f;for (auto &[key, val] : anyDictionary){if (val.type() == typeid(int))std::cout << "int: " << std::any_cast<int>(val) << "\n";else if (val.type() == typeid(std::string))std::cout << "string: " << std::any_cast<std::string>(val) << "\n";else if (val.type() == typeid(float))std::cout << "float: " << std::any_cast<float>(val) << "\n";}// std::vector ile ozellikleri tutmak icin ornek kullanimstruct Property{Property();Property(const std::string &, const std::any &);std::string mName;std::any mValue;};typedef std::vector<Property> PropertList;
-
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.