#include <iostream> int main() { int *pi; pi = new int; *pi = 3; std::cout << "pi : " << *pi << std::endl; return 0; }
Yukarıdaki örnekte bir adet int kadar alan ( sizeof(int) ) sistemden tahsis ediliyor. Daha sonra içerisine bir tam sayı atanıyor ve standart çıktıya basılıyor. Fakat bir hata var, tahsis edilen alan delete global operatörü kullanılarak geri bırakılmamış, unutulmuş. Doğrusu şöyle olmalıydı.
#include <iostream>
int main()
{
int *pi;
pi = new int;
*pi = 3;
std::cout << "pi : " << *pi << std::endl;
delete pi;
return 0;
}
Öyle ki bazen hiç bir şey olmaz, bir süre kullanırsınız, daha sonra patlamaya başlar. Kodun ilgili bölümüne artık konsantre olmadığınızdan dolayı böceği yakalamak çok vakit alabilir.
İşte bu durumu ortadan kaldıran akıllı göstericiler uzun zamandır hayatımıza girmiş bulunuyor. Akıllı gösterici kendisine verdiğiniz adresin tuttuğu alanı otomatikman sisteme geri iade eder. Sizin ayrıca delete ile sisteme geri iade işlemini yapmanıza gerek yoktur. Basit olarak başlangıç fonksiyonunda (constructor) veya atama operatör (assign operator) fonksiyonunda kopyalanan adresin bitiş fonksiyonunda (destructor) otomatikman delete işlemine sokulmasıdır.
İki çeşit akıllı gösterici tipinden bahsetmek istiyorum.
- Kopyalanamayan akıllı göstericiler
- Kopyalanabilen akıllı göstericiler
Kopyalanamayan akıllı göstericiler kopyalanabilenlerden daha hızlıdırlar. Bununla beraber kullanımları biraz daha sancılıdır. Basit bir örnekle başlayalım. Standard template library (stl) içindeki auto_ptr sınıfını kullanacağız.
#include <iostream>
#include <memory>
int main()
{
std::auto_ptr<int> p1(new int);
*p1.get() = 3;
std::cout << "p1.get() : " << *p1.get() << std::endl;
return 0;
}
Yukarıdaki örnekte herhangi bir delete işlemi yapmadık. auto_ptr sınıfı otomatikman kendi bitiş (destructor) fonksiyonunda delete işlemini gerçekleştirdi. Eminiz ki tahsis edilen alan sisteme geri bırakıldı. (Buradaki emin olma duygusu gerçekten çok önemli!)
Bu sınıf içerisindeki adresin başka bir auto_ptr sınıfına kopyalanamamasının nedeni tahmin edebileceğiniz gibi iki kere delete işlemine otomatikman girmesini engellemek. Eğer kopyalanabilseydi
Bu sınıf içerisindeki adresin başka bir auto_ptr sınıfına kopyalanamamasının nedeni tahmin edebileceğiniz gibi iki kere delete işlemine otomatikman girmesini engellemek. Eğer kopyalanabilseydi
{
copyable_auto_ptr<int> p1(new int);
copyable_auto_ptr<int> p2(p1)
*p1.get() = 3;
}
blok sonunda hem p1 için hem de p2 için delete işlemi aynı adres üzerinden tahsis edilmiş alanı boşaltmaya çalışacaktı ve böylece patlama gerçekleşecekti.
Peki ben aynı adresi bir yerde değil de birden fazla yerde saklamak ve kullanmak istiyorsam ne olacak? auto_ptr çözümü güzel ama her zaman efektif değil.
O zaman şu auto_ptr içerisinde bir sayaç olsa da benim için kaç kopya olduğu bilgisini tutsa, en son kalan kopyanın bitiş (destructor) fonksiyonunda otomatik olarak delete operatör fonksiyonunu çağırsa ne güzel olurdu değil mi?
Kulağa çok hoş gelen bu özelliği boost kütüphanesinin shared_ptr sınıfı destekliyor.
Seni seviyorum boost - I love you boost - Я люблю тебя boost
Bu kadar tezahürat yeter sanırım.
Kopyalanabilen akıllı göstericiler kopyalanamayanlara göre daha yavaş olsalar da aynı anda birden çok kopya saklama avantajını ellerinde bulundururlar. boost kütüphanesinin shared_ptr sınıfı kendi içerisinde bir sayaç tutar. Her bir kopyalama işleminde bu sayacı bir arttırır ve her bir bitiş (destructor) fonksiyonu çağırıldığında bu sayacı bir azaltır. Sayaç sıfır olduğunda nesnenin artık sisteme geri verimesi gerektiği anlaşılır ve delete işlemi gerçekleşir. Dolayısı ile her bir shared_ptr sınıfının bitiş (destructor) fonksiyonunda delete işlemi yapılmaz, sadece son nesne için yapılır.
#include <iostream> #include <boost/shared_ptr.hpp> int main() { boost::shared_ptr<int> p1(new int); *p1 = 3; boost::shared_ptr<int> p2(p1); std::cout << "p1 : " << *p1 << std::endl; std::cout << "p2 : " << *p2 << std::endl; return 0; }
Yukarıdaki örnekte p1 ve p2 akıllı göstericileri aynı nesnenin adresini gösteriyorlar. İkisi de kullanılabilir. Geri bırakma esnasında p2 akıllı göstericisinin bitiş (destructor) fonksiyonu daha önce çağırılacaktır (daha sonra yaratıldığı için) ve içerisindeki sayacı bir eksiltecektir. p1 nesnesinin bitiş fonksiyonu ise önce sayacı bir eksiltecektir sonra sayacı kontrol edecektir. Sayacın değerinin sıfır olduğunu görünce delete işlemini gerçekleştirecektir.
Java'da bulunan Garbage Collector sistemi gibi güvenilir ama ne zaman silineceği belli olan (Java'da Garbage Collector'ün ne zaman delete işlemini yapacağını bilmiyoruz) harika bir sistem.
Hata Nesneleri ile Akıllı Göstericiler
Akıllı göstericilerin bir güzel özelliği de hata nesnesi oluşumunda ortaya çıkar. Bir hata oluştuğunda fonksiyon bitmeden yukarı doğru throw işlemi gerçekleşir. detele işleminiz fonksiyonun sonunda ise ilgili kod kısmını try - catch bloğuna almanız, throw'u yakalamanız ve dinamik bölgeyi delete işlemine soktukran sonra mevcut throw işemini yukarı göndermeniz gerekir. Bunu yapmazsanız aşağıdaki gibi bir durum oluşur.
#include <iostream> void a_func_send_throw() { throw 20; } int main() { int *pi; pi = new int; *pi = 3; a_func_send_throw(); std::cout << "pi : " << *pi << std::endl; delete pi; return 0; }
a_func_send_throw fonksiyonu throw işlemini gerçekleştiriyor ve delete işlemi gerçekleşmiyor. Kodun akış yönü a_func_send_throw fonksiyonun gönderdiği throw sayesinde değişiyor. Şöyle yapabilirdik.
#include <iostream> void a_func_send_throw() { throw 20; } int main() { int *pi; pi = new int; *pi = 3; try { a_func_send_throw(); } catch (int e) { delete pi; return 0; } std::cout << "pi : " << *pi << std::endl; delete pi; return 0; }
Görüldüğü gibi bu çok sancılı bir yöntem. Her türlü kontrol için bir sürü işlem yapmanız gerekiyor.
C++'ın bir özelliği throw anında mevcuttaki nesnelerin bitiş (destructor) fonksiyonlarının çağırılmasıdır. Böylece throw işlemi yukarı doğru tırmanmadan nesnenizin bitiş fonksiyonu çağırılarak delete işlemi otomatik olarak gerçekleştirilir.
#include <iostream> #include <boost/shared_ptr.hpp> void a_func_send_throw() { throw 20; } int main() { boost::shared_ptr<int> pi(new int); *pi = 3; a_func_send_throw(); std::cout << "pi : " << *pi << std::endl; return 0; }
Artik dinamik olarak tahsis edilmiş alanımızın throw anında dahi sisteme iade edildiğinden eminiz.
boost kütüphanesinin shared_array sınıfı aynı shared_ptr gibidir fakat tek farkı new operatör fonksiyonu yerine new [] operatör fonksiyonunu kullanır ve bitiş (destructor) fonksiyonu içerisinde delete operatör fonksiyonu yerine delete [] operatör fonksiyonu çağırılır.
boost kütüphanesinin scoped_ptr sınıfı da vardır. Bu sınıf aynı stl kütüphanesinin auto_ptr sınıfı gibidir. Yani içerisinde sayaç yoktur. Eğer new yerine new [] kullanmak isterseniz scoped_array size yardımcı olacaktır.
Bu güzel göstericileri bol bol kullanacağınız mutlu kodlamalı günler dilerim :)
Volkan Özyılmaz
No comments:
Post a Comment