std::any sınıfı

Öyle bir nesne olsun ki istediğimiz herhangi türden bir değeri tutabilsin. İstediğimiz zaman nesnemizin tuttuğu değeri herhangi türden bir değer olarak değiştirebilelim. C++17 standartları ile dile eklenen std::any sınıfı işte bu işe yarıyor.

C++ dilinin sağladığı en önemli avantajlardan biri tür güvenliği (type safety). Yazdığımız kodlarda değer taşıyacak nesnelerimizi bildirirken onların türlerini de belirtiyoruz. Derleyici program bu bildirimlerden edindiği bilgi ile nesne üzerinde hangi işlemlerin yapılabileceğini derleme zamanında biliyor ve kodu buna göre kontrol ediyor. C++ dilinde değer taşıyan nesnelerin türleri programın çalışma zamanında hiçbir şekilde değişmiyor.

std::any sınıfı herhangi bir türden değer tutabilirken bir değer türü (value type) olarak tür güvenliği de sağlıyor. any bir sınıf şablonu değil. Bir any nesnesi oluşturduğumuzda onun hangi türden bir değer tutacağını belirtmemiz gerekmiyor. any türünden bir nesne herhangi bir türden değeri tutabilirken sahip olduğu değerin türünü de biliyor. Peki bu nasıl mümkün oluyor? Yani nasıl oluyor da bir nesne herhangi türden bir değeri saklayabiliyor? Bunun sırrı any nesnesinin tuttuğu değerin yanı sıra bu değere ilişkin typeid değerini de (typeinfo) tutuyor olması.

any sınıfının tanımı any isimli başlık dosyasında:

std::any nesnelerini oluşturmak

Bir any sınıf nesnesi belirli türden bir değeri tutacak durumda ya da boş olarak yani bir değer tutmayan durumda hayata getirilebilir:

any sınıf nesnesinin kurucu işlevine gönderilen argümandan farklı türden bir değeri tutabilmesi için kurucu işlevin ilk parametresine standart <utility> başlık dosyasından tanımlanan in_place_type<> argümanının gönderilmesi gerekiyor. any tarafından tutulacak nesnenin kurucu işlevine birden fazla değer gönderilmesi durumunda da yine in_place_type<> çağrıdaki ilk argüman olmalı:

std::make_any<> yardımcı işlevi

any türünden bir nesne oluşturmanın bir başka yolu da make_any<> yardımcı fabrika işlevini kullanmak. Burada any nesnesinin tutacağı değerin türü şablon tür argümanı olarak seçildiğinden in_place_type<> yardımcısının kullanılması gerekmiyor:

std::any nesneleri için bellek ihtiyacı

Bir any sınıf nesnesi tarafından tutulacak değerin bellek gereksinimi (storage) 1 byte da olabilir 5000 byte da. any nesnesi sahip olacağı değeri tutmak için heap alanında bir bellek bloğu edinebilir. Bu konuda derleyiciler istedikleri gibi kod üretebiliyorlar. Derleyiciler tipik olarak doğrudan any nesnesi içinde bir bellek alanını  görece olarak küçük nesnelerin tutulması amaçlı kullanıyorlar. (C++17 standartları da böyle bir gerçekleştirimi öneriyor.) Eğer any tarafından saklanacak değer bu bellek alanına sığıyor ise değer bu alanda tutuluyor. Bu tekniğe “küçük tampon optimizasyonu” (small buffer optimization SBO) deniyor. Saklanacak nesne bu bellek alanına sığmıyor ise heap alanından bir bellek bloğu elde ediliyor. Aşağıda programı kendi derleyiciniz ile derleyerek çalıştırın ve any nesneleri için sizeof değerinin ne olduğunu görün:

Benim farklı derleyiciler ile yaptığım testlerin sonucu şöyle oldu:

std::any nesnesinin değerini değiştirmek

Sınıfın atama operatör işlevi ya da emplace<> işlev şablonu ile bir any sınıf nesnesinin değeri değiştirilebilir. Aşağıdaki kodu inceleyin:

std::any nesnesini boşaltmak

Bir değer tutan any nesnesini boşaltmak için sınıfın reset isimli işlevi çağrılabilir:

Bu çağrı ile any türünden a değişkeni eğer boş değil ise a değişkeninin tuttuğu nesnenin hayatı sonlandırılıyor. Bu işlemden sonra a değişkeni boş durumda. any nesnesini boşaltmanın bir başka yolu da ona varsayılan kurucu işlev (default constructor) ile oluşturulmuş bir geçici nesneyi atamak:

Atama aşağıdaki gibi de yapılabilir:

std::any nesnesinin boş olup olmadığını sınamak

Bir any nesnesinin boş olup olmadığı yani bir değer tutup tutmadığı sınıfın has_value isimli üye işleviyle sınanabilir. (boost::any sınıfında olan empty üye işlevi yerine neden has_value işlevinin tercih edilmiş olması ilginç)

Aşağıdaki koda bakalım:

std::any_cast<> işlev şablonu

any sınıf nesnesinin tuttuğu değere erişmenin tek yolu onu global any_cast<> işleviyle tuttuğu değerin türüne dönüştürmek. any_cast<> işleviyle, any sınıf nesnesi bir değer türüne bir referans türüne ya da bir pointer türüne dönüştürülebilir:

std::bad_any_cast

any_cast<> ile yapılan dönüşüm başarısız olursa yani dönüşümdeki hedef tür any nesnesinin tuttuğu tür ile aynı değilse bad_any_cast sınıfı türünden bir hata nesnesi gönderilir:

Burada gönderilen bad_any cast sınıfı için türetme hiyerarşisi şöyle:

any_cast<> dönüştürme işlevini kullanarak any tarafından tutulan nesneye gösterici (pointer) semantiği ile de erişilebilir. Ancak bu durumda şablon argümanı olarak kullanılan tür tutulan nesnenin türü değil ise bir hata nesnesi gönderilmez (exception throw edilmez), nullptr değeri elde edilir:

tutulan nesnenin türünü öğrenmek

Sınıfın type isimli üye işlevi ile any tarafından tutulmakta olan nesnenin türü öğrenilebilir:

İşlevin geri dönüş değeri any nesnesinin tuttuğu değerin tür bilgisini taşıyan type_info nesnesi.  Eğer any nesnesi boş ise type işlevinin geri dönüş değeri typeid(void) olur. Bu işlevle erişilen type_info nesnesi type_info sınıfının operator== işleviyle bir karşılaştırma işlemine sokulabilir. Aşağıdaki kodu inceleyelim:

std::any sınıfı ve taşıma semantiği

any sınıfı taşıma (move) semantiğini de destekliyor. Ancak taşıma semantiğinin desteklenmesi için tutulan değere ilişkin türün kopyalama semantiğini de desteklemesi gerekiyor. unique_ptr<T> gibi kopyalamaya kapalı ancak taşımaya açık türlerden değerler (movable but not copyable) any nesneleri tarafından tutulamazlar. Aşağıdaki kodda string nesnesinden any nesnesine ve any nesnesinden string nesnesine yapılan taşıma işlemlerini görebilirsiniz:

std::any sınıfının kullanıldığı yerler

C++17 standartları öncesinde C++ dilinde yazılan kodlarda daha önce void* türünün kullanıldığı birçok yerde any sınıfı kullanılabilir. void * türünden bir gösterici (pointer) değişken, herhangi türünden bir nesnenin adresini tutabilir. Ancak void* türünden bir değişken adresini tuttuğu nesnenin türünü bilmez ve onun hayatını kontrol edemez. Ayrıca void * türü bir gösterici türü olduğu için “deger türü” (value type) semantiğine sahip değildir. any istenilen herhangi türden bir değeri saklayabilir. Tutulan nesnenin değeri ve türü değiştirilebilir. any tuttuğu nesnenin hayatını da kontrol eder ve her zaman tuttuğu nesnenin türünü bilir. Eğer tutulacak değerin hangi türlerden olabileceği biliniyorsa any yerine variant türünün kullanılması çok daha uygun olacaktır. Aşağıdaki kullanım örneği resmi öneri metninden alındı:

Yukarıdaki kodda tanımlanan property türünden bir nesne hem istenilen türden bir değer saklayabilir hem de bu değere ilişkin tanımlayıcı bir yazıyı tutabilir. Böyle bir tür GUI uygulamalarından oyun programlarına kadar birçok yerde kullanılabilir. Bir kütüphanenin ele alacağı türleri bilmeden o türlerden değerleri tutabilmesi ve başka API‘lere bunları gönderebilmesi gereken durumlarda any sınıfı iyi bir seçenek oluşturabilir. Betik (script) dilleriyle arayüz oluşturma, betik dilleri için yazılan yorumlayıcı programlarda böyle türlere ihtiyaç artabiliyor.

any sınıfının tasarımında büyük ölçüde Kelvin Henney tarafından yazılan ve 2001 yılında boost kütüphanesine eklenen boost::any sınıfı esas alındı. Kevlin Henney ve Beman Dawes 2006 yılında WG21/N1939=J16/06-0009 belge numarasıyla any sınıfının standartlara eklenmesi önerisini sundular. Nihayet Beman Dawes ve Alisdair Meredith‘in önerileriyle diğer kütüphane bileşenleriyle birlikte any sınıfı da C++17 standartları ile dile eklendi. boost::any kütüphanesinde olmayan, emplace işlevi, in_place_type_t<> parametreli kurucu işlev, küçük tampon optimizasyonu yapılabilmesi gibi bazı özellikler std::any kütüphanesine yer alıyor.

Share

Necati Ergin

C ve Sistem Programcıları Derneğinde eğitmen olarak çalışıyor.

Bunlar da ilginizi çekebilir

Kod Eklemek İçin Okuyun