unique_ptr sınıf şablonu – 1

C++11 standartları ile birlikte standart kütüphaneye dahil edilmiş olan unique_ptr türü bir akıllı gösterici (smart pointer) sınıfıdır. Bu akıllı gösterici sınıfı genel olarak “tek sahiplik” (exclusive ownership) stratejisini uygulamaktadır. Bir unique_ptr nesnesi bir dinamik sınıf nesnesini gösteren tek bir gösterici olarak kullanılır. Bir unique_ptr nesnesi, kendi hayatı sona erince sahibi olduğu dinamik sınıf nesnesinin de hayatını sonlandırarak onun tutmakta olduğu kaynakların serbest bırakılmasını sağlar. Bu sınıfın temel varlık nedenlerinden biri, bir hata nesnesi (exception) gönderildiğinde söz konusu olabilecek kaynak sızıntısının (resource leak) engellenmesidir.

unique_ptr sınıfı C++ 98 standartlarında var olan ancak dildeki araçların yetersizliğinden kaynaklanan kötü tasarımı nedeniyle eleştirilen auto_ptr sınıfının yerine getirilmiştir. C++11 standartları ile auto_ptr sınıfı kullanımdan düşürülmüş (deprecated) onun yerine hem daha yalın ve daha net bir arayüze sahip olan hem de daha düşük kodlama hatası riski içeren unique_ptr sınıfı standart kütüphaneye eklenmiştir. auto_ptr sınıfının tasarlandığı dönemde C++ dili taşıma semantiği, değişken sayıda tür parametresine sahip şablonlar (variadic templates) gibi araçlara sahip değildi. C++11 standartlarıyla dile kazandırılan bu araçlar unique_ptr sınıfının güvenli bir biçimde tasarlanmasına olanak sağlamıştır.

unique_ptr sınıfının kullanımı

Bazı işlevler işlerini şu şekilde görür:

  • Önce işlerini gerçekleştirebilmek için sınıf nesneleri yoluyla bazı kaynaklar edinirler.
  • Sonra yüklendikleri işleri gerçekleştirirler.
  • İşlerini tamamladıktan sonra edindikleri kaynakları geri verirler.

İşlev içinde edinilen kaynaklar, işlev içinde tanımlanan yerel sınıf nesnelerine bağlanmışlarsa, işlevin kodundan çıkıldığında yerel sınıf nesnelerinin sonlandırıcı işlevinin çağrılmasıyla tutulan kaynaklar geri verilmiş olur. Ancak kaynaklar yerel bir sınıf nesnesine bağlanmadan dinamik olarak dışsal biçimde edinildiğinde dinamik sınıf nesnesini yöneten gösterici değişkenler tarafından kontrol edilirler. Bu durumda dinamik nesnenin hayatı delete ifadesi ile sonlandırılır.

Aşağıdaki örneği inceleyin:

Böyle bir işlev sorunlara yol açabilir. Sorunlardan biri dinamik nesnenin hayatının sonlandırılmasının (delete edilmesinin) unutulmasıdır. Öneğin delete işleminden önce bir return deyimi yürütülürse dinamik nesnenin hayatı sonlandırılmayacaktır.

Başlangıçta kolayca görülüyor olmasa da bir başka sorun da bir hata nesnesinin (exception) gönderilmesidir. Bir hata nesnesi gönderildiğinde programın akışı işlevden çıkacak böylece delete deyimi yürütülmeyecektir. Bu durum bir bellek sızıntısına (memory leak) neden olabileceği gibi daha genel olarak bir kaynak sızıntısına (resource leak) yol açabilir.

Gönderilebilecek tüm hata nesnelerinin yine işlev tarafından yakalanması oluşabilecek kaynak sızıntısı engelleyebilir:

Bir hata nesnesinin gönderilmesi durumunda da kaynakların güvenli bir şekilde geri verilmesi sağlanmak istenirse hem daha fazla kod yazılması gerekir hem de  yazılan kod çok daha karmaşık hale gelir. Dinamik olarak yaratılan nesnelerin sayısı birden fazla ise çok daha karışık bir durumun oluşacağı açıktır. Hata oluşumuna açık olan ve gereksiz bir karmaşıklığa neden olan bu kötü kodlama stilinden kaçınılmalıdır.

Bu amaçla tasarlanabilecek bir akıllı gösterici (smart pointer) sınıfı sorunu çözebilir. Bir akıllı gösterici nesnesinin kendi sonlandırıcı işlevinin çağrılmasıyla, akıllı göstericinin yönettiği dinamik nesnenin de hayatı sonlandırılabilir. Yerel bir akıllı gösterici nesnesi söz konusu olacağından artık işlevden ister normal yollarla ister bir hata nesnesi gönderilmesi yoluyla (exception) çıkılsın akıllı gösterici nesnesine bağlanmış olan dinamik nesnenin hayatı sonlanacak, böylece kaynak sızıntısı oluşmayacaktır.
İşte unique_ptr sınıfı bu amaçla tanımlanmış bir akıllı gösterici sınıfıdır.

unique_ptr sınıfı türünden bir nesne, gösterdiği dinamik nesnenin tek sahibi durumundadır.
unique_ptr nesnenin hayatı sonlandığında yani bir unique_ptr nesnesinin sonlandırıcı işlevi çağrıldığında bu nesnenin sahip olduğu dinamik nesnenin de hayatı sonlandırılır, yani o dinamik nesne delete edilir.

unique_ptr nesnesinin mülkiyetindeki dinamik nesnenin tek bir sahibi vardır ve bu semantik yapı kullanıcı kodlar tarafından da sürdürülmeli ve korunmalıdır.

Daha önceki örneğe geri dönüyouz:

Hepsi bu kadar. Artık hata yakalamaya ilişkin deyimlere gerek kalmadığı gibi delete işleci de kullanılmıyor.

bir unique_ptr nesnesinin kullanımı

unique_ptr sınıf şablonu bir göstericinin özelliklerini destekleyen bir arayüze sahiptir :
İçerik işlecinin kullanılmasıyla unique_ptr nesnesinin gösterdiği dinamik nesneye erişilebilir.
unique_ptr nesnesinin gösterdiği dinamik nesnenin öğelerine ok işleciyle erişmek de mümkündür. Aşağıdaki kodu inceleyin:

Ancak unique_ptr sınıf şablonunun arayüzünde gösterici aritmetiğine ilişkin ++ ve -– işlevleri, toplama ve çıkarma işlevleri ile köşeli ayraç işlevi yer almamaktadır.

unique_ptr sınıfının kurucu işlevi explicit olduğundan bu türden bir nesne kopyalayan ilk değer verme (copy initialization) sözdizimiyle başlatılamaz.

Bir unique_ptr nesnesi dinamik bir nesneye sahip olmadan da varlığını sürdürebilir. Varsayılan kurucu işlev ile hayata getirilen unique_ptr nesnesi hiçbir dinamik nesnenin sahibi değildir.

Bir unique_ptr nesnesine nullptr değeri doğrudan atanabileceği gibi bu amaçla sınıfın reset isimli üye işlevi de çağrılabilir:

Bu durumda eğer unique_ptr nesnesi bir dinamik nesneye sahipse sahip olduğu finamik nesneyi delete eder.

Sınıfın release isimli üye işlevi bir unique_ptr nesnesinin sahip olduğu dinamik nesnenin sahipliğini bırakır. Bu işlev doğrudan unique_ptr nesnenin kontrol ettiği dinamik nesnenin adresini döndürmektedir. Bu işlevin çağrılmasıyla artık dinamik nesnenin sorumluğunu bu işlevi çağıran kod üstlenir:

Sınıfın bool türüne dönüşüm yapan üye işleviyle bir unique_ptr nesnesinin dinamik bir nesneyi kontrol edip etmediği sınanabilir:

Bir unique_ptr nesnesinin dinamik bir nesnenin sahibi olup olmadığı unique_ptr nesnesninin nullptr değerine eşitliği ile de sınanabilir:

unique_ptr nesnesininin veri elemanı olarak tuttuğu ham göstericinin değerinin nullptr değerine eşitliğiyle de aynı sınama gerçekleştirilebilir:

unique_ptr ile sahipliğin devredilmesi

unique_ptr sınıfı tek sahiplik semantiğini uygular.
Sınıfın kopyalayan kurucu işlevi (copy constructor) ve kopyalayan atama işlevi (copy assignment function) delete edilerek sınıf kopyalamaya karşı kapatılmıştır.
Ancak birden fazla unique_ptr nesnesinin aynı dinamik nesnesini adresiyle başlatılmaması programcının sorumluluğundadır:

Yukarıdaki gibi bir kod çalışma zamanı hatasına neden olur. Kodlayıcıların böyle hatalardan kaçınması gerekir.
Peki, unique_ptr sınıfının kopyalayan kurucu işlevi ve atama işlecinin kodu nasıl olmalı? Bir unique_ptr nesnesini kopyalama semantiğiyle hayata başlatamamayız ve bir unique_ptr nesnesine kopyalama semantiğiyle atama yapamayız.
unique_ptr sınıfında taşıma semantiği kullanılmaktadır. Taşıyan kurucu işlev ve taşıyan atama işlevi sahipliğin devredilmesini sağlar:

Kopyalayan kurucu işlevin kullanıldığını düşünelim:

İlk deyimden sonra, up1 new işleciyle hayata getirilmiş nesnenin sahibi olur.
Kopyalayan kurucu işlev gerektiren ikinci deyim sentaks hatasıdır. İkinci bir unique_ptr nesnesinin aynı dinamik Myclass nesnesinin sahipliğini almasına izin verilmez. Aynı zamanda tek bir sahibe izin verilmektedir. Ancak üçüncü deyimle sahiplik up1 nesnesinden up3 nesnesine devredilir. Artık up1 nesnesi sahipliği bırakmıştır.  new işleciyle hayata getirilmiş Myclass nesnesi up3′ün hayatının bitmesiyle delete edilir. Atama işleci de benzer şekilde davranır:

Burada atama operatör işlevi sahipliği up1 nesnesinden up2 nesnesine devreder. Sonuç olarak, daha önce sahibi up1 olan dinamik nesnenin artık yeni sahibi up2‘dir.
C++11 öncesinde kullanılan auto_ptr sınıfında bu işlem doğrudan kopyalama semantiği ile yapılıyor bu da bir çok soruna neden oluyordu.
Eger up2 nesnesi atamadan önce bir dinamik nesnenin sahibi olsa idi bu dinamik nesne atamadan önce delete edilecekti:

Yeni bir sahiplik edinmeden sahip olduğu nesneyi bırakan bir unique_ptr nesnesi hiçbir nesneyi göstermez.
Bir unique_ptr nesnesnine başka bir unique_ptr nesnesinin değeri taşınarak tanmalıdır. unique_ptr nesnelerine normal adresler atanamaz.

Bir unique_ptr nesnesine nullptr değerinin atanması nesnenin reset işlevinin çağrılmasına eşdeğerdir.

Bu yazı Nicolai M. Jossutis‘in The C++ Standard Library isimli kitabının unique_ptr isimli bölümünün serbest çevirisidir.

Share

Necati Ergin

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

Bunlar da ilginizi çekebilir

Kod Eklemek İçin Okuyun