Boost Akıllı Göstergeleri

Giriş

Bir önceki yazımda auto_ptr'ın yeterli olmadığı durumlar için boost.org'un akıllı göstergelerini önermiştim.[1] Bu yazıda Boost akıllı göstergelerinin beş tanesini kısa örneklerle tanıtmaya çalışacağım:

Boost'a sonradan eklenmiş olan intrusive_ptr şablonunu, daha aşağıda sıraladığım nedenlerden dolayı bu yazının konusu dışında bırakacağım.

Boost Kütüphanesi

Standart kütüphaneler bir çok nedenden ötürü kısıtlıdırlar; akla gelen her olanak standartta yer alamaz:

Boost kütüphaneleri, çeşitli nedenlerle standart C++ kütüphanesinin kapsamı dışında kalmış olan bir grup sınıf, sınıf şablonu, ve makrolar topluluğudur.

Boost kütüphaneleri tamamen gönüllüler tarafından önerilir, gerçeklenir, denetlenir, ve kullanıma sunulur.

Rassal sayı üretimi, Python diliyle uyum, platformdan bağımsız işletim dizisi (thread) programcılığı ve düzenli ifadeler gibi çok sayıdaki olanak arasından benim kendi adıma en çok yararlandıklarım; akıllı göstergelerdir.

Boost Başlıkları

Boost kütüphaneleri hemen hemen tamamen başlık kütükleri içinde tanımlanmışlardır.[2] Bu yüzden, kütüphanelerin büyük çoğunluğunu yalnızca başlıklarını edinerek kullanabilirsiniz. Kullanmaya başlamadan önce, istediğiniz bir yere kopyalamak dışında herhangi bir kurulum işlemi yapmanıza gerek yoktur.

Bütün başlık kütükleri 'boost' dizini altında ve 'hpp' uzantıları ile yer alırlar.

Boost Ad Alanı

Standart kütüphanedeki adların 'std' ad alanı içinde tanımlanmış olmalarına benzer şekilde, Boost kütüphanesindeki adlar da 'boost' ad alanı içinde tanımlanmışlardır.

O yüzden, o olanakları kullanırken ya tam adlarını yazmak, ya da 'using' bildirimleri veya 'using' komutları kullanmak gerekir.

Bu üç yöntemi, kolayca tür dönüştürme amacıyla kullanılan boost::lexical_cast'i kullanarak göstermek istiyorum:

#include <iostream>
#include <string>
#include <boost/lexical_cast.hpp>

using namespace std;

/*
  Bu islev lexical_cast'i tam adiyla kullaniyor
 */
void tamsayi(string const & yaziyla)
{
    int sayi = boost::lexical_cast<int>(yaziyla);
    cout << (sayi * 2) << '\n';
}

/*
  Bu islev lexical_cast'in tam adini yazmak zorunda
  degil, cunku bir 'using' bildirimi kullaniyor
 */
void kesirliSayi(string const & yaziyla)
{
    /*
      Bu islev icinde 'lexical_cast' goruldugunde
      onun 'boost::lexical_cast' oldugu anlasilsin
      diye...
     */
    using boost::lexical_cast;

    /*
      Burada 'boost::' kullanmak zorunda degiliz...
     */
    double sayi = lexical_cast<double>(yaziyla);    
    cout << (sayi + 0.6) << '\n';
}

/*
  Bu noktadan sonraki islevler acikca  'boost::'
  yazmak zorunda degiller cunku bir 'using' komutu
  kullaniliyor...
 */

using namespace boost;

/*
  Bu islev yalnizca 'lexical_cast' yaziyor ve onun
  ne oldugu anlasiliyor.
 */
void karakter(string const & yaziyla)
{
    char k = lexical_cast<char>(yaziyla);
    ++k;
    cout << k << '\n';
}    

int main()
{
    tamsayi("123");
    kesirliSayi("4.5");
    karakter("a");
}

Ben yazının geri kalanında 'using' komutu yöntemini kullanacak ve her seferinde 'boost::' yazmaktan kurtulacağım.

Boost akıllı göstergeleri sınıf şablonudurlar

Bu göstergeler, her türle çalışabilsinler diye sınıf şablonu olarak gerçeklenmişlerdir. Hatırlarsanız, sınıf şablonlarını kullanırken şablon parametrelerini açıkça belirtmek gerekir.

Onun için, örneğin 'int' türünden bir nesneden sorumlu olan bir scoped_ptr'ı 'int' türünü açılı parantezler arasında belirterek şöyle tanımlarız:

    scoped_ptr<int> p = /* ... */

scoped_ptr

'Scope' İngilizce'de kapsam anlamına gelir. C++ dünyasında bir kod bloğu bir kapsamdır.

scoped_ptr, 'new' ile ayrılan bir bellekte yaşayan ve bir kapsamdan çıkıldığında silinmesi gereken nesneler için kullanılır. Asıl işi, kendisine emanet edilen adresi, kendi yaşamı sonlanırken 'delete' işlecine göndermektir.

'delete'in işi de bir bellekte bulunan nesnenin bozucusunu çağırmak ve o belleği geri vermek olduğu için, scope_ptr kullanarak nesne temizliği hatalarını ve bellek sızıntılarını önlemiş oluruz.

Bu "akıllılığının" dışında, sıradan bir gösterge gibi de çalışır. get() öge işlevi yardımıyla yalın gösterge bekleyen işlevlere rahatça gönderilebilir.

scoped_ptr masrafsız ve basit bir sınıf şablonu olarak tasarlanmıştır. Onun için, atama işleci veya kopyalayıcı tanımlamaz.

Öte yandan, iki tane özel öge işlev sunar:

1) T * get() const;

Sorumluluğunda bulunan nesnenin adresini döndürür.

2) void reset(T * p = 0);

Sorumluluğunda bulunan nesneyi silmesi ve yeni bir nesnenin sorumluluğunu üstlenmesi için kullanılır.

Bu bilgileri kullanan bir örnek program şöyle yazılabilir:

#include <iostream>
#include <boost/scoped_ptr.hpp>

using namespace std;
using namespace boost;

// Oylesine bir yapi

struct Yapi
{
    int i;
    double d;
};


// Yapi kullanan oylesine bir islev

void Yapi_yazdir(Yapi const * yapi)
{
    cout << "(" << yapi->i
         << ", " << yapi->d << ')';
}

void Yapi_deneme()
{
    /*
      Bellekte bir Yapi olusturuyor ve onun
      sorumlulugunu bir scoped_ptr'a veriyoruz:
     */
    scoped_ptr<Yapi> gosterge(new Yapi);

    /*
      'gosterge'yi bir yalin gosterge gibi
      kullanabiliriz. Oge erisim isleci olan -> ile:
    */
    gosterge->i = 1;
    gosterge->d = 1.1;

    /*
      Bir Yapi nesnesi adresi bekleyen bir isleve
      gonderebiliriz.

      Nesnenin adresini 'get' oge islevi ile ediniyoruz:
     */
    Yapi_yazdir(gosterge.get());
    cout << '\n';

    /*
      Gosterdigi nesneye * isleciyle erisebiliriz.
      
      (Burada nesneyi anlamsizca kendisine atiyoruz;
      ama olsun...)
     */
    *gosterge = *gosterge;

    /*
      O nesneyi birakip yeni bir tanesini tutmasini
      istiyoruz
     */
    gosterge.reset(new Yapi);

    // Simdi rastgele sayilar gorecegiz:
    Yapi_yazdir(gosterge.get());
    cout << '\n';

    /*
      Bu islevin tanimlamis oldugu kapsam nasil
      sonlanirsa sonlansin; ornegin islevden

      - sonuna gelindigi icin
      - bir 'return' satiri ile,
      - atilan bir aykiri durum yuzunden

      cikilmasi durumlarinda, 'gosterge'nin tuttugu
      nesne mutlaka silinecektir.

      Bunu, Yapi yapisi icin

      - kurucu,
      - bozucu,
      - atama isleci ve
      - kopyalayici

      tanimlayarak ve onlara girildiginde ekrana
      yazilar yazdirarak gorebilirsiniz.
     */
}

void ikiye_katla(int * p)
{
    *p *= 2;
}

/*
  Bu da 'int' turunu gosteren bir scoped_ptr ornegi
 */
void int_deneme()
{
    scoped_ptr<int> p(new int);
    *p = 1000;
    ikiye_katla(p.get());
    cout << *p << '\n';
}

int main()
{
    Yapi_deneme();
    int_deneme();
}

Aslında bu örnekler scoped_ptr'ın kullanımını göstermek için biraz zorlama oldular. Çünkü örneklerdeki Yapi ve int nesnelerini dinamik bellekten almanın gereği yoktur. Onun yerine otomatik değişken kullanmak daha doğal olacaktır:

  Yapi yapi;

ve

  int sayi;

gibi... Burada çokşekilliliğin gerektiği bir örnek daha uygun olabilirdi. Aşağıdaki örnekte, hayvanYap işlevinin dinamik bellekten alarak döndürdüğü belleği hemen bir scoped_ptr'a geçirerek o belleğin otomatik olarak geri verilmesini sağlamış oluyoruz.

#include <boost/scoped_ptr.hpp>

using namespace boost;

struct Hayvan
{
    virtual ~Hayvan()
    {}
};

struct Kedi : public Hayvan {};
struct Kopek : public Hayvan {};

Hayvan * hayvanYap()
{
    return new Kedi;
}

int main()
{
    scoped_ptr<Hayvan> hayvan(hayvanYap());
}

scoped_array

scoped_array, scoped_ptr'ın 'new[]' işleciyle alınan belleklerle çalışan türüdür. Ondan farkları şunlardır:

1) Kendisi silinirken 'delete'i değil, 'delete[]'i çağırır. Onun için, sorumluluğuna verilen belleğin 'new[]' ile alındığından emin olmak gerekir.

2) Kolaylıkla dizi gibi kullanılabilsin diye [] işlecini de sunar.

Aslında scoped_array yerine ondan daha becerikli olan std::vector de kullanılabilir. Ancak, onun gerekmediği durumlar için çok hızlı ve basit bir topluluk olarak düşünülebilir.

Şimdi de scoped_array kullanan bir örnek:

#include <iostream>
#include <algorithm>
#include <iterator>
#include <boost/scoped_array.hpp>

using namespace std;
using namespace boost;

void deneme()
{
    size_t const uzunluk = 10;
    scoped_array<double> dizi(new double[uzunluk]);

    for (size_t i = 0; i != uzunluk; ++i)
    {
        // Kolaylikla dizi gibi kullanabiliyoruz
        dizi[i] = i * 1.1;
    }

    // Standart cikisa kopyaliyoruz
    
    copy(dizi.get(), dizi.get() + uzunluk,
         ostream_iterator<double>(cout, " "));
    cout << '\n';

    /*
      Burada bellegi geri vermeye gerek yok;
      'dizi', kendi yasami sona ererken sorumlusu
      oldugu nesneleri de silecek.
    */
}

int main()
{
    deneme();
}

Daha önce de değindiğim gibi, scoped_ptr ve scoped_array tek bir kapsam içinde kullanılmak üzere tasarlandıkları için, ne kopyalanabilirler ne de atanabilirler.

Başka bir yazıda da değindiğim gibi, nesnelerin standart topluluklarla kullanılabilmeleri için kopyalanabilmeleri gerekir.[3] scoped_ptr ve scoped_array bu özellikleri sağlamadıkları için standart topluluk üyesi olarak kullanılamazlar.

Boost kütüphanesinin hem standart topluluklarla kullanılabilecek hem de sorumlusu olduğu belleği geri verebilecek akıllı göstergeleri shared_ptr ve shared_array'dir.

shared_ptr

scoped_ptr'ın kopyalanamadığı için standart topluluklarla kullanılamamasının yanında, birden fazla scoped_ptr'ın aynı nesneyi göstermesi de olanaksızdır.

Dahası, scoped_ptr tasarımı gereği, sorumlusu olduğu nesneyi kendi bulunduğu kapsam sonlanırken siler.

scoped_ptr'ın karşılayamadığı akıllı gösterge ihtiyaçları için shared_ptr kullanılır. 'Shared'in Türkçe karşılığı 'paylaşılan'dır. Aynı nesneyi gösteren shared_ptr'lar, tek bir nesneyi gösterirler. Nesnenin silinmesi işinin sorumluluğunu birlikte paylaşırlar.

Gösterdikleri nesneye ek olarak, o nesneyi toplam kaç tane shared_ptr'ın gösterdiğini de bilirler.

Her shared_ptr kendisi silinirken, o toplamı bir eksiltir. Bu eksiltme sırasında o sayı sıfıra düştüğünde, eksiltmeyi yapan shared_ptr nesneyi de siler. Yani nesneyi silme işi onu gösteren son shared_ptr'a kalmıştır.

Benzer olarak, shared_ptr'ların kopyalanması durumunda nesnenin kendisi kopyalanmaz; onu gösteren shared_ptr'ların toplamı bir arttırılır.

Bu toplamın nasıl artıp azaldığını gösteren bir örneği shared_ptr'ın benim bugüne kadar hiç kullanmak zorunda kalmadığım use_count işlevi ile göstermek istiyorum:

#include <iostream>
#include <boost/shared_ptr.hpp>

using namespace std;
using namespace boost;

class Hayvan
{
    /*
      Atama ve kopyalamayi yasaklamak icin bunlari
      ozel erisim alaninda bildiriyoruz
     */
    Hayvan(Hayvan const &);
    Hayvan & operator= (Hayvan const &);

public:
    
     Hayvan() { cout << "kuruldu\n"; }
    ~Hayvan() { cout << "silindi\n"; }
};

typedef shared_ptr<Hayvan> HayvanGostergesi;

void bilgiVer(HayvanGostergesi const & gosterge, char const * baslik)
{
    cout << baslik << ": "
         << gosterge.use_count() << '\n';
}

void foo(HayvanGostergesi gosterge)
{
    bilgiVer(gosterge, "foo'ya girildi");
    HayvanGostergesi gosterge2 = gosterge;
    bilgiVer(gosterge, "gosterge kopyalandi");
}

void bar(HayvanGostergesi gosterge)
{
    bilgiVer(gosterge, "bar'a girildi");
    foo(gosterge);
    bilgiVer(gosterge, "foo'dan cikildi");
}

int main()
{
    HayvanGostergesi gosterge(new Hayvan);

    bilgiVer(gosterge, "bar cagrilacak");
    bar(gosterge);
    bilgiVer(gosterge, "bar'dan cikildi");
}

Bu program benim çalıştığım ortamda şu çıktıyı verdi:

kuruldu
bar cagrilacak: 1
bar'a girildi: 2
foo'ya girildi: 3
gosterge kopyalandi: 4
foo'dan cikildi: 2
bar'dan cikildi: 1
silindi

shared_ptr'ı şimdi de bir standart topluluk içinde kullanmak istiyorum. Ben shared_ptr'ı zaten en çok böyle durumlarda kullanıyorum.

Dikkat ederseniz, aşağıdaki program bellek temizliğiyle hiç ilgilenmiyor bile. O iş shared_ptr'lar tarafından hallediliyor.

Bu program, çokşekillilik anlatılırken çok karşılaşılan 'şekil' tanımlama kavramının çok kaba bir örneği oldu. Başka sınıflar türeterek geliştirmek isteyebilirsiniz.

Programda benim ilgimi çeken bir nokta, Cizgi::cizOzel_ islevini özyineleme kullanarak yazmam oldu. O işlev her seferinde ekrana tek bir nokta koymasına rağmen, özyineleme nedeniyle bütün çizgi beliriveriyor.

Programı sırf bu özelliği yüzünden bile incelemek isteyebilirsiniz.

#include <iostream>
#include <vector>
#include <iterator>
#include <boost/shared_ptr.hpp>
#include <math.h>

using namespace std;
using namespace boost;

char const bosYer = ' ';
char const kenar = '|';
char const altUstKenar = '-';

class Kagit;

/*
  Bu siniftan tureyen her alt sinifin kendisine
  has cizme isini goren cizOzel_ islevini
  tanimlamasi gerekir
 */
class Sekil
{
    /*
      Sekli cizerken kullanilan karakter
     */
    char karakter_;

    /*
      Sekli belirtilen kagida belirtilen
      karakterle cizen islev
     */
    virtual void cizOzel_(Kagit &, char karakter) const = 0;
    
public:

    explicit Sekil(char karakter)
        :
        karakter_(karakter)
    {}

    /*
      Sanal islevi olan her sinifin olmasi
      gerektigi gibi bunun da bozucu islevini
      virtual yapiyoruz
     */
    virtual ~Sekil()
    {}

    void ciz(Kagit & kagit) const
    {
        cizOzel_(kagit, karakter_);
    }
};

class Nokta : public Sekil
{
    double satir_;
    double sutun_;

    virtual void cizOzel_(Kagit &, char) const;

public:

    Nokta(double satir, double sutun, char karakter)
        :
        Sekil(karakter),
        satir_(satir),
        sutun_(sutun)
    {}

    /*
      Kendisine verilen noktayla arasindaki
      noktayi dondurur
     */
    Nokta araNokta(Nokta const & diger, char karakter) const
    {
        return Nokta(fabs((satir_ + diger.satir_) / 2),
                     fabs((sutun_ + diger.sutun_) / 2),
                     karakter);
    }

    /*
      Burada yaklasik bir is yapiyoruz. Noktalar
      birbirlerine belirli bir miktar yakinda
      olduklarinda esit kabul ediliyorlar.
     */
    bool operator== (Nokta const & diger) const
    {
        double const satirFarki = satir_ - diger.satir_;
        double const sutunFarki = sutun_ - diger.sutun_;

        return sqrt((satirFarki * satirFarki)
                    +
                    (sutunFarki * sutunFarki)) < 0.5;
    }
};

class Cizgi : public Sekil
{
    Nokta bas_;
    Nokta son_;
    
    virtual void cizOzel_(Kagit & kagit, char karakter) const
    {
        /*
          Cizgiyi kolay geldigi icin ozyineleme
          yonteminden yararlanarak cizmeye karar
          verdim. Mutlaka bundan daha iyi
          yontemler vardir.

          Buradaki amac, yalnizca cizginin iki
          ucunun ortasinda kalan noktayi cizmek.

          Ondan sonra, cizgiyi o noktadan ikiye
          bolunmus gibi dusunerek yeni olusan iki
          kisa cizginin de orta noktasini ciziyor
          ve bu isi cizginin uzunlugu sifir olana
          kadar yineliyoruz.
         */
        Nokta const orta = bas_.araNokta(son_, karakter);
        orta.ciz(kagit);

        if (bas_ == son_) return;
        
        Cizgi(bas_, orta, karakter).ciz(kagit);
        Cizgi(orta, son_, karakter).ciz(kagit);
    }

    friend ostream & operator<< (ostream & os, Cizgi const &);

public:

    Cizgi(Nokta const & bas,
          Nokta const & son,
          char karakter)
        :
        Sekil(karakter),
        bas_(bas),
        son_(son)
    {}
};

typedef vector<Nokta> Noktalar;
typedef Noktalar::const_iterator NoktaErisici;

class Poligon : public Sekil
{
    Noktalar noktalar_;

    /*
      Burada ardarda gelen her iki noktayi ('bas'
      ve 'son') cizgi olarak goruyor ve teker
      teker o cizgileri cizdiriyoruz.
     */
    virtual void cizOzel_(Kagit & kagit, char karakter) const
    {
        if (noktalar_.size() < 2) return;

        NoktaErisici const ilkNokta = noktalar_.begin();
        NoktaErisici bas = ilkNokta;

        for (NoktaErisici son = bas + 1;
             son != noktalar_.end();
             bas = son, ++son)
        {
            Cizgi const cizgi(*bas, *son, karakter);
            cizgi.ciz(kagit);
        }

        /*
          Sonuncu noktayi ilk noktaya bagliyoruz
         */
        Cizgi const cizgi(*bas, *ilkNokta, karakter);
        cizgi.ciz(kagit);
    }

public:

    Poligon(Noktalar const & noktalar, char karakter)
        :
        Sekil(karakter),
        noktalar_(noktalar)
    {}
};

/*
  Sekillerin cizildigi kagidin bir satirini temsil
  ediyor
 */
class Satir
{
    vector<char> karakterler_;
    
    friend ostream & operator<< (ostream &, Satir const &);
    
public:

    explicit Satir(size_t uzunluk)
        :
        karakterler_(uzunluk, bosYer)
    {}

    size_t size() const
    {
        return karakterler_.size();
    }

    char & operator[] (size_t hangisi)
    {
        return karakterler_[hangisi];
    }
};

ostream & operator<< (ostream & cikis, Satir const & satir)
{
    cikis << kenar;
    
    copy(satir.karakterler_.begin(),
         satir.karakterler_.end(),
         ostream_iterator<char>(cikis, ""));

    cikis << kenar;

    return cikis;
}

/*
  Sekillerin cizildigi kagidi temsil ediyor
 */
class Kagit
{
    vector<Satir> satirlar_;

    friend ostream & operator<< (ostream &, Kagit const &);
    
public:

    Kagit(size_t toplamSatir, size_t satirUzunlugu)
        :
        satirlar_(toplamSatir, Satir(satirUzunlugu))
    {}

    /*
      Kagidin belirli bir noktasini boyama isini
      goruyor. Kagidin disinda kalan noktalar
      gozardi ediliyorlar.
     */
    void boya(size_t satir, size_t sutun, char karakter)
    {
        if ((satir < satirlar_.size())
            &&
            (sutun < satirlar_[0].size()))
        {
            satirlar_[satir][sutun] = karakter;
        }
    }
};

/*
  Bu islev kagidin sinirlarini gostermek icin
  kullanilan yatay cizgi ciziyor
 */
void yatayCizgi(ostream & cikis, size_t uzunluk)
{
    fill_n(ostream_iterator<char>(cikis, ""),
           uzunluk , altUstKenar);
    cikis << '\n';
}

ostream & operator<< (ostream & cikis, Kagit const & kagit)
{
    size_t const cizgiUzunlugu = kagit.satirlar_[0].size() + 2;
    
    yatayCizgi(cikis, cizgiUzunlugu);
           
    copy(kagit.satirlar_.begin(),
         kagit.satirlar_.end(),
         ostream_iterator<Satir>(cikis, "\n"));

    yatayCizgi(cikis, cizgiUzunlugu);

    return cikis;
}

void Nokta::cizOzel_(Kagit & kagit, char karakter) const
{
    kagit.boya(static_cast<size_t>(satir_),
               static_cast<size_t>(sutun_),
               karakter);
}

/*
  Sonunda bu yazinin konusuyla dogrudan ilgili
  olan noktaya gelebildik.

  Bu programda 'Sekil'lerden olusan bir toplulukla
  ilgileniyoruz (main'in icindeki 'sekiller'
  toplulugu). O toplulugun aslinda ne tur
  sekillerden olustuklari ile ilgilenmedigimiz
  icin, onlari ancak dinamik bellekte yasayan
  nesneler olarak gosterebiliriz.

  Dinamik bellekten ayrilan yerlerde yasayan bu
  nesnelerin yasam sureclerini belirleyebilmek ve
  onlarla isimiz bittiginde otomatik olarak
  silinmelerini saglamak icin shared_ptr turunu
  kullaniyoruz.

  Sinif sablonlarini kullanirken her zaman
  yaptigimiz gibi'typedef' yoluyla okunakli adlar
  takmak, burada da isimizi kolaylastiriyor.

  Programin buradan sonrasinda soyut bir
  'SekilGostergesi' turuyle ugrasacak ve onlarin
  temizlikleriyle ilgilenmek zorunda kalmayacagiz.
 */

typedef shared_ptr<Sekil> SekilGostergesi;
typedef vector<SekilGostergesi> Sekiller;
typedef Sekiller::const_iterator SekilErisici;

/*
  Bu uc islevi oylesine sekiller uretmek icin
  kullaniyorum. Siz programi degistirerek baska
  sekiller olusturmak isteyebilirsiniz.
 */
SekilGostergesi ucgen(char karakter)
{
    Noktalar noktalar;
    noktalar.push_back(Nokta(5, 15, karakter));
    noktalar.push_back(Nokta(15, 30, karakter));
    noktalar.push_back(Nokta(15, 15, karakter));
    
    return SekilGostergesi(new Poligon(noktalar, karakter));
}

SekilGostergesi cizgi(char karakter)
{
    Nokta const bas(8, 8, karakter);
    Nokta const son(14, 2, karakter);
    
    return SekilGostergesi(new Cizgi(bas, son, karakter));
}

SekilGostergesi nokta(char karakter)
{
    return SekilGostergesi(new Nokta(5, 6, karakter));
}

int main()
{
    Sekiller sekiller;
    sekiller.push_back(nokta('@'));
    sekiller.push_back(cizgi('*'));
    sekiller.push_back(ucgen('.'));
    
    Kagit kagit(20, 40);
    
    for (size_t i = 0; i != sekiller.size(); ++i)
    {
        sekiller[i]->ciz(kagit);
    }

    cout << kagit << '\n';
}

shared_array

scoped_ptr ile scoped_array ilişkisinde olduğu gibi, shared_array'in shared_ptr'dan farkı, sorumlusu olduğu nesneyi 'delete' yerine 'delete[]' ile silmesidir. Onun için 'new[]' ile oluşturulan nesnelerin sorumluluğunu üstlenebilir.

Bu şablonu da kullanmaya karar vermeden önce, onun yerine std::vector veya std::string gibi başka üst düzey soyutlamaların daha uygun olup olmadığını araştırmakta yarar vardır.

Küçük bir örnek:

#include <iostream>
#include <string>
#include <boost/shared_array.hpp>
#include <ctype.h>

/*
  Kullandigimiz bir kutuphanenin 'new[]' ile
  aldigini belgeledigi bir bellek alani donduren
  soyle bir islevi olsun

  Not: Evet, burada std::string dondurmek daha
  uygundur. Kutuphanenin herhangi bir nedenden
  dolayi oyle yapmadigini varsayalim
 */
char * baskenti(std::string const & /* ulke */)
{
    /*
      ...  Burada bir arama yaptigimizi varsayalim
    */

    char const baskent[] = "ankara";
    char * sonuc = new char[sizeof(baskent)];
    strcpy(sonuc, baskent);
    return sonuc;
}

typedef boost::shared_array<char> BaskentGostergesi;

BaskentGostergesi basHarfiBuyukBaskent(std::string const & ulke)
{
    /*
      baskenti islevinin dondurdugu bellegi
      'delete[]' ile geri vermek gerektigi icin,
      burada shared_ptr degil, shared_array
      kullaniyoruz.

      Baska islem yapmadan once sorumlulugu
      gecirmek, programin guvenligini arttirmis
      oluyor.
     */

    BaskentGostergesi baskent(baskenti(ulke));
    baskent[0] = toupper(baskent[0]);

    return baskent;
}

int main()
{
    BaskentGostergesi gosterge = basHarfiBuyukBaskent("Turkiye");
    
    std::cout << gosterge.get() << '\n';
}

Yukarıdaki programda açıklamalarda da değindiğim gibi, yapılabiliyorsa std::string türünü kullanmak genelde daha iyi bir seçimdir.

weak_ptr

Bu tür göstergeler, bir veya daha fazla shared_ptr tarafından sahiplenilen bir nesneyi, sahiplenme işine karışmadan göstermek için kullanılırlar. Adındaki 'zayıf' anlamına gelen 'weak', herhalde bu konuda zayıf kaldığı için kullanılmıştır.

Gözlemci örüntüsünün (observer design pattern) bir uygulamasıdır. shared_ptr'lar tarafından gösterilen bir nesnenin silinmesi kararı alındığında, o nesneyi gösteren weak_ptr'lar kendiliklerinden sıfır değerini alarak geçersiz hale gelirler.

Bir weak_ptr'ın, gösterdiği nesneyi gösteren diğer shared_ptr'larla arasında şöyle bir "gözleme" ilişkisi olduğunu düşünebilirsiniz: "Sizin gösterdiğiniz nesne silineceği zaman bana haber verin, ben de kendimi geçersiz hale getireyim".

Aşağıdaki program, bir nesnenin yaşam sürecinden sorumlu üç tane shared_ptr'a karşılık bir tane weak_ptr içeriyor. shared_ptr'lar silindiklerinde weak_ptr geçersiz hale geliyor.

#include <boost/shared_ptr.hpp>
#include <boost/weak_ptr.hpp>
#include <vector>
#include <iostream>
#include <algorithm>

using namespace std;
using namespace boost;

typedef shared_ptr<double> Gosterge;
typedef weak_ptr<double> ZayifGosterge;

/*
  Bu islev, bir akilli gostergenin gosterdigi
  nesnenin adresini yazdirmak icin kullaniliyor
 */
template <class T>
void adresBildir(T gosterge)
{
    cout << static_cast<void *>(gosterge.get()) << '\n';
}

// weak_ptr'i kullanan bir islevimiz olsun

void kullan(ZayifGosterge zayif)
{
    cout << "Zayif gostergenin gosterdigi nesnenin adresi: ";
    adresBildir(zayif);

    /*
      weak_ptr'in gosterdigi nesnenin belirli bir
      anda hala yasayip yasamadigini weak_ptr::get
      islevi ile ogrenebiliriz
     */
    if (zayif.get())
    {
        cout << "Asil nesne hala yasadigi icin kullanabilirim\n";
        *zayif = 3.4;
    }
    else
    {
        cout << "Asil nesne  silinmis; kullanamam!\n";
    }
}

int main()
{
    /*
      Gostergelerden olusacak bir topluluk
      olusturalim.

      Topluluktaki ilk gostergeyi bellekten
      ayirdigim bir nesneyi gosterecek sekilde
      olusturuyorum.

      Topluluktaki sonraki gostergeleri ise
      birinci nesnenin kopyalari olarak ekliyorum.
    */
    vector<Gosterge> gostergeler;
    gostergeler.push_back(Gosterge(new double(1.2)));
    gostergeler.push_back(gostergeler[0]);
    gostergeler.push_back(gostergeler[0]);

    cout << "Topluluktaki " << gostergeler.size()
         << " gostergenin gosterdikleri adresler sunlar:\n";
    
    for_each(gostergeler.begin(),
             gostergeler.end(),
             adresBildir<Gosterge>);
    
    /*
      Simdi de onlarin gosterdigi nesneyi gosteren
      bir de weak_ptr olusturuyoruz.
    */
    ZayifGosterge zayif(gostergeler[0]);
    kullan(zayif);

    cout << "Toplulugu bosaltiyoruz...\n";
    gostergeler.clear();
    
    kullan(zayif);
}

Dikkat ederseniz weak_ptr kullanan programların onları kullanmadan önce, geçerli olup olmadıklarını get() işlevi ile denetlemeleri gerekir.

intrusive_ptr

shared_ptr, nesneyi gösteren göstergelerin sayısını dinamik bellekten ayırdığı bir yerde tutar. Elimizde bu sayıyı tutacak zaten bir sayaç bulunduğu durumlarda intrusive_ptr türünü kullanabilir ve ayrıca bir sayaç ayrılmasını engellemiş oluruz.

Normal olarak genelde shared_ptr kullanıldığı halde, intrusive_ptr'ı şu durumlarda kullanmak isteyebilirsiniz:

intrusive_ptr'ı bu yazının konusu dışında bırakmaya karar verdim. Bir çok nedenim var: Zaten öncelikle shared_ptr'ı yeğlemek gerekir; Nispeten yeni olan intrusive_ptr'ı daha önce kendim hiç kullanmadım; Yazı zaten yeterince uzadı; boost.org'da yeterli örnekler bulamadım; daha fazla yazmak istemiyorum :)

Özet

Akıllı göstergeler, C++'ın ayrılmaz bir parçası olan RAII yönteminin dinamik bellek alanları ile uygulanmalarıdır.

Boost'un akıllı göstergeleri, henüz standartta yer almasalar da onun bir uzantısı olarak görülebilirler:

Bu türleri kullanarak programlarımızda bellekleri açıkça geri verme hatasına düşmekten kurtuluruz.

Referanslar

[1] Akıllı göstergelerle ilgili daha genel bilgi edinmek için şu yazıdan yararlanabilirsiniz: http://ceviz.net/index.php?case=article&id=263

[2] Boost'un regexp ve python kütüphanelerinin baştan derlenmiş olmaları gerekir. Ancak, eğer onları kullanmaya gerek duymuyorsanız, diğer başlıkları kodunuza ekleyerek Boost kütüphanelerini hemen kullanmaya başlayabilirsiniz.

[3] Standart topluluklarla ilgili genel bilgi edinmek için şu yazıdan yararlanabilirsiniz: http://ceviz.net/index.php?case=article&id=184

[4] vector, map ve multi_map toplulukları ile ilgili yazılara da şu sayfadan erişebilirsiniz: http://ceviz.net/index.php?case=category&id=34


Ali Çehreli - acehreli@yahoo.com