PDA

Tam Sürümünü Görmek İçin : "Türden bağımsız bir toplama algoritması"na ek


acehreli
20/06/2003, 22:20
Selamlar,

Volkan Uzun "Türden bağımsız bir toplama algoritması" yazısında eksik bıraktığım bazı konulara değinmiş. Burada devam ediyorum...

--- volkan <...> wrote:
> selamlar,
> ceviz.netteki yazıyı okudum. stl konusuna oldukca uzak biri
> olarak kafama bir soru takıldı.
> aslında sorunun pek stl le alakası da yok sanırım :)

Evet yok, şablonlarla ilgili genel bir soru.

> şimdi türden bağımsız bir algoritma ile fonksiyonumuz yazdık,
> ve artık derleme anında değilde çalışma anında artık tipler
> belirnerek fonsiyonumuz düzenlenip cagiralacak.

Hayır, işlev ve sınıf şablonlarının kodları derleme zamanında kullanılırlar. Derleyici, o kodları kullanarak, şablonların her bir kullanımı için değişik kod üretir. Yani aşağıdaki 'toplam' işlevini 'char' ve 'int' türleri için iki kere kullanan bir programda derleyici, iki değişik işlev oluşturur ve onları derler.

Birbirlerinden bağımsız olan o iki işlevin kimlikleri (signature) şöyle olur:


char toplam<char>(char const *, char const *);
int toplam<int >(int const *, int const *);


Derleyicini böyle her tür için ayrı kod üreterek programın boyutunu arttırmasına kod şişmesi (code bloat) denir. (Bunun kısmen önüne geçmenin bazı yolları vardır.)

C++ durağan türlü bir dildir (statically typed (evet, kötü bir çeviri oldu :)). Şablon kullanımı, dilin bu özelliğini bozmaz.

> diyelimki ben bir class yazdım ve bu class'in + operatörü için
> tanımlı bir fonksiyonu yok.
> bu durumda toplamı cagirirsam oluscak hatayı nasıl anlayip
> engellerim ??!?

Önce küçük bir nokta: Her ne kadar soru genel olsa da, 'toplam' şablonunda toplama işleci (operator+) değil, arttırma işleci (operator+=) kullanılıyor.

Asıl yazıda da çok uzağından değinmeye çalıştığım gibi, aslında şablonlar tamamen türden bağımsız değillerdir. Volkan'ın da belirttiği gibi, her şablon ancak bazı şartları sağlayan türlerle kullanılabilir.

> yani :
>


> #include <iostream>
>
> using std::cout;
>
> class sacma_class
> {
> private:
> int a,b,c,d;
> public :
> int get_all();
> };
> template <class Tur>
> Tur toplam(Tur const * bas, Tur const * son)
> {
> Tur sonuc = 0;
>
> for ( ; bas != son; ++bas)
> {
> sonuc += *bas;
> }
>
> return sonuc;
> }


> şimdi main içinden toplam(sacma_class a,sacma _class b )
> cagirimi bir hata ama ben bunu nasıl anlayuip onlem alırım ?

Derleyicinin kodları derleme zamanında kullanıyor olması bu konuda işimize yarar. Şablonları o şablonun gerektirdiği niteliklere sahip olmayan türlerle kullanmaya kalktığımızda derleme hatası alırız.

Yukarıdaki kodu eksiklerini giderdikten sonra aşağıdaki program içinde kullanacağım.

Önce, yapacağım bir değişikliğin nedenini söyleyeyim. Ben de çoğu C++ programcısı gibi, öğe nesnelerin adlarının sonuna bir '_' karakteri koyarım. Böyle yaptığım zaman, onları öğe işlevler içerisinde kolayca ayırdedebiliyorum. Aksi taktirde, en azından kurucu işlevde sorunlar yaşıyorum. Örneğin 'a' öğesine ilk değerini getiren parametreye ne diyeceğimi şaşırıyorum. Onun için, öğe adlarını değiştirdim.


#include <iostream>

using std::cout;

template <class Tur>
Tur toplam(Tur const * bas, Tur const * son)
{
/*
Asil yazida kullandigimin aksine,
burada 'sonuc' nesnesini varsayilan kurucu
ile kuracagim. Asil yazida

Tur sonuc = 0;

yazmistim. Oyle yazmak, bu algoritmanin
kullanilabilecegi turlerin sayisini
azaltir.

Cunku, o satirin kullanilabilmesi icin,
turun bir 'int' alan (veya bir 'int'e
donusturulebilecek bir tur alan) bir kurucusunun
olmasi gerekir. Ustelik, o kurucunun 'explicit'
de olmamasi gerekir.

Asil yazida yaptigim bu dar goruslulugu :)
burada duzeltiyor ve varsayilan kurucusu olan
her turle calisabilecek sekilde yaziyorum:
*/

Tur sonuc = Tur();

/*
Yukaridakini

Tur sonuc();

seklinde yazmak daha dogal olurdu. Ancak,
o zaman o satir ne yazik ki bir islev
bildirimi olarak anlasilirdi:

Tur donduren, hicbir parametre almayan,
ve adi 'sonuc' olan bir islev!

Varsayilan kurucuyla nesne olusturmaya calisirken
boyle yanlislikla islev bildirimi yapmak, C++'in
cok dusulen tuzaklarindan birisidir.

(Ben de bu ornegi yazarken bu tuzaga belki
de bir milyonuncu kere dustum :o)
*/

for ( ; bas != son; ++bas)
{
sonuc += *bas;
}

return sonuc;
}

/*
Yukaridaki algoritma ile kullanilabilecek
turlerin sunlari saglamalari gerekir:

- varsayilan kurucu: Bunu biz yazacagiz

- operator+=(Tur const &): Bunu biz yazacagiz

- kopyalama kurucusu: Bunu yazmamiza gerek yok;
derleyicinin yazdigi kopyalayici
bu sinif icin yeterli olacaktir.

(Cunku bu sinifin sahip oldugu
ve sonradan geri verecegi bir
kaynak yok.)
*/

class sacma_class
{
private:

int a_,b_,c_,d_;

public :

// Varsayilan kurucu
sacma_class()
:
a_(0), b_(0), c_(0), d_(0)
{}

sacma_class(int a, int b, int c, int d)
:
a_(a), b_(b), c_(c), d_(d)
{}

/*
Arttirma isleci, sagdakinin her ogesinin degerini
o ogeye karsilik gelen ogeye eklesin.
*/
sacma_class & operator+= (sacma_class const & sagdaki)
{
a_ += sagdaki.a_;
b_ += sagdaki.b_;
c_ += sagdaki.c_;
d_ += sagdaki.d_;

return *this;
}

void yazdir(ostream & cikis) const
{
cikis << a_ << ' ' << b_ << ' '
<< c_ << ' ' << d_;
}
};


/*
operator+= islecinin geleneksel yazis bicimi
yukaridaki gibidir.

operator+ ise, operator+= isleci kullanarak yazilir.
Bu ornekte hicbir ise yaramasa da, cok kisa oldugu
icin, ek bir bilgi olarak onu da veriyorum:
*/

sacma_class operator+ (sacma_class const & soldaki,
sacma_class const & sagdaki)
{
/*
Asagidaki satir, once soldakinin bir kopyasini
alir, sonra sagdakini ona ekler, ve sonucu dondurur.
*/
return sacma_class(soldaki) += sagdaki;
}

#define OGE_ADEDI(x) (sizeof(x) / sizeof(*(x)))

int main()
{
sacma_class const sacmalar[] =
{
sacma_class(11, 12, 13, 14),
sacma_class(21, 22, 23, 24)
};

sacma_class const sonuc =
toplam(sacmalar, sacmalar + OGE_ADEDI(sacmalar));

sonuc.yazdir(cout);
cout << '\n';
}


Ali