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 nedeni, 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 nesneninsahip 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ı türünden bir nesneye ilkdeğer verilmesinde atama işlecinin kullanılması geçerli değildir:

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:

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 uygularr. 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 atanmalı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.

Necati Ergin

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

Bunlar da ilginizi çekebilir

unique_ptr sınıf şablonu – 1” için 10 yorum

  1. Hocam yazılarınızdan bolca faydalanıyoruz. Emeğinize sağlık.

    Sadece C++11 ve C++14 ile gelen yenilikler için özel bir kurs açarsanız seviniriz.

  2. std::unique_ptr uptr(new std::string(“Maya”));
    (*up)[0] = ’K’; // Yazının ilk karakteri değiştiriliyor
    up->append(“can”); // Yazının sonuna karakterler ekleniyor.
    ——————————————-
    (*uptr)[0] = ‘K’; // Yazının ilk karakteri değiştiriliyor
    uptr->append(“can”); // Yazının sonuna karakterler ekleniyor.
    uptr olması gerekiyor Necati Bey. Yazım yanlışı olmuş galiba.

    1. Evet isim yanlış yazılmış, ayrıca başlık dosyası eklenmemiş. İki hatayı da düzelttim. Teşekkür ederim.

  3. #include
    #include

    int main()
    {
    // oluşturulan unique_ptr nesnesine dinamik bir string nesnesi ile ilkdeğer veriliyor:

    std::unique_ptr uptr(new std::string(“Maya”));
    (*up)[0] = ’K’; // Yazının ilk karakteri değiştiriliyor
    up->append(“can”); // Yazının sonuna karakterler ekleniyor.
    std::cout << *uptr << std::endl; // yazı yazdırılıyor.

    return 0;
    }
    koduna #include eklenemsi gerekiyor yoksa std::cout << *uptr << std::endl; satırında hata alınıyor.

    1. include string
      eklenmesi gerekiyor. büyük-küçük işaretleri arasındaki karakterler yorumda otomatik olarak silinmiş.

  4. Selamlar Necati Bey,
    std::string* sp = new std::string(“hello”);
    std::unique_ptr std::string up1(sp);
    std::unique_ptr std::string up2(sp); // Yanlış: up1 ve up2 aynı dinamik nesneye sahip

    up2 nesnesi hayata gelirken sp’nin gösterdiği dinamik nesnenin unique_ptr türünden bir göstericisi olduğu bilgisi nerede tutuluyor(bir tablo mu var?) veya arka planda hatayı fırlatan dinamik nesnenin kendisi(benim unique_ptr türünden bir göstericim var deyip kendisi mi hataya neden oluyor) mi?

    1. Burada derleme zamanında bir hata yok. Ayrıca çalışma zamanında da bir hata nesnesi (exception) gönderilmiyor. Bu durum çalışma zamanı hatası (runtime error). up1 nesnesinin hayatı bittiğinde “hello” yazısına sahip dinamik string nesnesi delete edilecek. Bu durumda sp2 nesnesi dinamik nesneyi kullanmak istediğinde ya da sp2’nin de hayatı bittiğinde çalışma zamanı hatası oluşacak. Çünkü sp2 nesnesi için çağrılan sonlandırıcı işlev ,zaten delete edilmiş dinamik string nesnesini yeniden delete etme girişiminde bulunmuş olacak.

      1. std::string* sp = new std::string(“hello”);
        std::unique_ptr up1(sp);
        std::unique_ptr up2(sp);
        up2.reset(); //delete edilmiş dinamik string nesnesini yeniden delete etme girişimini engellemek için koydum.
        return 0;

        Benzer hatayı gene aldım. Bu yukarıdaki açıklamayı geçersiz mi yapıyor?

        1. Bu da çalışma zamanı hatası. up2 reset edildiğinde sahibi olduğu dinamik nesneyi delete edecek. up1’in hayatı bitince, up1 için çağrılan sonlandırıcı işlevi delete edilmiş dinamik nesneyi yine delete edilmeye çalışacak.
          Sonuç: Aynı dinamik nesneyi gösteren birden fazla unique_ptr oluşturmak doğru değil.

  5. unique_ptr exception safety mi?
    ptr = std::unique_ptr Myclass (new Myclass);
    işleminde new işlemi sırasında heap bölgesinde alan tahsisatı sırasında bir hata oluştu mu oluşmadı mı kontrolünü
    if(ptr == nullptr) benzeri bir kod yazmamız mı gerekiyor?

Bir Cevap Yazın

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

Kod Eklemek İçin Okuyun