Aşağıdaki yazıyı daha önce bir forum sorusuna yanıt olarak göndermiştim. Biraz düzelterek ve değiştirerek C++'ın tür dönüşümü işleçlerini özetleyen bir yazı haline getirdim.


C++ tür dönüşümü işleçleri

C++'ta derleyicinin otomatik olarak yaptıklarının dışında beş tane tür dönüşümü vardır:

C'nin tür dönüşümü hâlâ en güvensizidir. C++'ın getirdikleri içinde de reinterpret_cast, davranışı çalışılan ortama bağlı olduğu için ve nesnelerin bitlerini değişik anlamda kullandırdığı için en güvensizidir.

C'den kalan tür dönüşümünü zaten hiç kullanmayın. Derleyici size bu konuda hiçbir yardımda bulunmaz. C++'ın getirdiklerini kullanırsanız, yanlış kullandığınız yerlerde derleme hataları ile uyarılabilirsiniz.

Aslında zaten tür dönüşümlerinden her zaman için uzak durmaya bakın. Tür dönüşümünün neden gerektiğini soruşturun ve kaçınmaya çalışın.

static_cast:

Derleme zamanında bilinen tür dönüşümleri için kullanılır. Programcının derleyiciye "ben bu nesnenin çalışma zamanında bile aslında şu türden olduğundan eminim" demesi gibidir. (Buradaki 'static' sözcüğü, derleme zamanını belirliyor.)

Derleyici yasal olmayan dönüşümlerde hata verir. Yine de programcının derleyiciye yalan söylememesi iyi olur :) İşte derleyiciye yanlış bilgi verdiğimiz için göçen bir program:

#include <iostream>

struct Hayvan
{};

class Kus : public Hayvan
{
    int * p_;
    enum { birSayi = 42 };

public:

    Kus()
        :
        p_(new int(birSayi))
    {}

    ~Kus()
    {
        delete p_;
    }

    void kanatCirp()
    {
        // Bellege erisiyoruz.
        // Eger bunu bir Fil icin yaparsak,
        // program buyuk olasilikla 'Segmentation
        // fault' verecektir.

        if (*p_ == birSayi)
        {
            std::cout << "Ucabiliyoruuum!\n";
        }
        else
        {
            std::cout << "(Eger bu noktada programgocmediyse)\n"
                      << "Kus degil miyim neyim?Dusuyoruuuuum!\n";
        }
    }
};

struct Fil : public Hayvan
{};

void agactanAtla(Hayvan & hayvan)
{
    // Ornegin bu programda agactanAtla
    // islevinin hep Kus turu icin
    // cagrildigindan eminiz.
    // (Oyle oldugunu saniyoruz.)

    Kus & kus = static_cast<Kus &>(hayvan);
    kus.kanatCirp();
}

int main()
{
    Kus kus;
    Fil fil;
    agactanAtla(kus);
    agactanAtla(fil);
}

Yukarıdaki örnekte, aslında Hayvan türüyle çalışan, ama belli bir noktada bir Kus'un sunduğu kanatCirp işlevini çağırması gereken agactanAtla adlı bir işlev var. Programcı, o noktada 'hayvan' değişkeninin aslında gerçekten bir Kus olduğunu düşünerek derleyiciye bunu bildiriyor. Derleyici de programı buna inanarak derliyor.

Çalışma zamanında, işlev ilk çağrıldığında çalışıyor ama ikincisinde göçüyor. Bu örneği, static_cast'in bir doğru, bir de yanlış kullanımını göstermek için verdim.

const_cast:

Aslında const ve/veya volatile olmadığını bildiğimiz, ama bir noktada karşımıza const ve/veya volatile olarak çıkan bir nesnenin const'lığını ve/veya volatile'lığını kaldırmak için kullanılır. Yine nesnenin gerçekten öyle olduğundan emin olmamız gerekir.

Bazı insanlar, volatile ile de çalıştığı için, adının bunu da belirtecek şekilde seçilmiş olması gerektiğini düşünürler. Örneğin 'cv_cast' gibi bir ad daha uygun olabilirmiş; çünkü C++ standardında const ve/veya volatile belirteçlerine de her birden 'cv-qualification' denir. Çoğunlukla const referans (veya gösterge) alması gerektiği halde kötü tasarım sonucu const olmayan referans (veya gösterge) alan işlevleri çağırmada kullanılır. Böyle işlevler bazı kütüphanelerde karşımıza çıkabilirler.

#include <iostream>

// Kotu tasarlanmis bir islev.
// Parametresinde degisiklik yapmadigi
// halde onu 'const' olarak tanimlamamis.

int toplam_a_harfi(char * dizgi)
{
    int toplam = 0;

    for ( ; *dizgi; ++dizgi)
    {
        if (*dizgi == 'a') ++toplam;
    }
    
    return toplam;
}

// Bu islev, parametresinde degisiklik
// yapmayacagi icin, onu dogru olarak
// 'const' olarak tanimlamis.

int toplam_a_harfinin_karesi(char const * dizgi)
{
    // Ama cagirdigi islev bunun kadar duyarli
    // olmadigi icin, o islevi cagirabilmek icin
    // const_cast kullaniyor

    char * d = const_cast<char *>(dizgi);
    int const toplam_a = toplam_a_harfi(d);
    return toplam_a * toplam_a;
}

int main()
{
    std::cout << toplam_a_harfinin_karesi("araba") << '\n';
}

dynamic_cast:

dynamic_cast; bir tür dönüşümü işleci olduğu kadar, çalışma zamanında sorulan bir soru gibi de algılanabilir: bir üst türün aslında belirli bir alt türden olup olmadığını öğrenmek için kullanılır (down-casting). Aslında bunun kullanılmasının uygun olduğu çoğu yerde ziyaretçi örüntüsü (visitor pattern) de kullanılabilir.

Eğer göstergeye dönüştürüyorsak; türü tutturduysak yasal bir gösterge, değilse 0 ediniriz.

Eğer referansa dönüştürüyorsak; türü tutturduysak program akışı normal olarak devam eder. Türü tutturamadığımız durumda ise; yasal olmayan referans (veya 'null-referans') diye bir kavram olmadığı için, std::bad_cast hatası atılır.

Onun için, hemen hemen her zaman göstergeyle kullanılır.

Bu düzeneğin çalışabilmesi için üst sınıfın en az bir tane sanal (virtual) işlev bildirmiş olması gerekir.

Aşağıdaki işlev, her tür hayvan için 'ilerle' işlevini çağırırken, özellikle bir Fil ile çalıştığında ek olarak 'kasin' (kaşın) işlevini de çağırıyor.

#include <iostream>
#include <typeinfo>

class Hayvan
{
public:
    // Soyut islev kullanarak
    // arayuzu belirliyoruz.
    // 
    // '= 0' yazarak bunu en alttaki siniflarin
    // kesinlikle tanimlamalari gerektigini bildiriyoruz.

    virtual void ilerle() const = 0;
};

class Kus : public Hayvan
{
public:
    virtual void ilerle() const
    {
        std::cout << "pirrr\n";
    }
};

class Fil : public Hayvan
{
public:
    virtual void ilerle() const
    {
        std::cout << "langir lungur\n";
    }

    void kasin() const
    {
        std::cout << "hart hurt\n";
    }
};


// dynamic_cast'i gostergeyle kullanmak daha kolay

void cokIlerlet_G(Hayvan const & hayvan, int kilometre)
{
    for (int i = 0; i != kilometre; ++i)
    {
        hayvan.ilerle();

        // Bakalim bu hayvan aslinda bir Fil miymis
        Fil const * fil = dynamic_cast<Fil const *>(&hayvan);
        if (fil)
        {
            // 'fil != 0' olduguna gore Filmis
            fil->kasin();
        }
    }
}

// dynamic_cast'i referansla kullanmak bana
// daha zor, daha yavas, ve gereksiz geliyor :)

void cokIlerlet_R(Hayvan const & hayvan, int kilometre)
{
    for (int i = 0; i != kilometre; ++i)
    {
        hayvan.ilerle();

        // Bakalim bu hayvan aslinda bir Fil miymis
        try
        {
            Fil const & fil = dynamic_cast<Fil const &>(hayvan);

            // Buraya gelebildigimize gore Filmis
            fil.kasin();
        }
        catch (std::bad_cast)
        {
            // Hata atildigina gore Fil degilmis
        }
    }
}

// Yukaridaki iki islevi gosterebilecek
// islev gostergesi turu
typedef void (*Islev)(Hayvan const &, int);

void dene(char const * baslik, Islev islev)
{
    Kus kus;
    Fil fil;

    int const uzaklik = 2;

    std::cout << "--- " << baslik << " ---\n";
    islev(kus, uzaklik);
    islev(fil, uzaklik);
}

int main()
{
    dene("gosterge ile", cokIlerlet_G);
    dene("referans ile", cokIlerlet_R);
} 

reinterpret_cast:

C++ türleri içinde en güvenilmez olanıdır. Davranışı da derleyiciye ve yanılmıyorsam çalışılan ortama bırakılmıştır. Eldeki nesnenin bit yapısını başka bir tür gibi değerlendirme (reinterpret) anlamına gelir.

struct S
{
    int i;
};

void foo(unsigned int i)
{
    // Calistigim ortamda nesne
    // gostergelerinin 'unsigned int'
    // ile ayni sayida bitten olustuklarini
    // bildigimi varsayalim.
    //
    // Bu ozel programda bana verilen
    // dogal sayinin aynen bir gosterge gibi
    // kullanilabileceginden eminim.
    //
     // E peki o zaman... :)

    S * s = reinterpret_cast<S *>(i);
    s->i = 0;
}

int main()
{
    S s;

    // Ayni durumun tersi de burada
    foo(reinterpret_cast<unsigned int>(&s));
}

C türü parantezli tür dönüşümü:

Bunu kullanmayın; yukarıdaki işleçlerden birisini kullanın. ;)

    // Kesinlikle kullanmayin!
    Fil * fil = (Fil *)hayvan;

C'den kalan tür dönüşümü işlecini C++'ta kullanmaya gerek yoktur. Onun yerine, yapmak istediğimize uygun olan ve yapmak istediğimizi açıkça belirten yukarıdaki dört işleçten birisini kullanın.

Derleyici C'den kalan tür dönüşümü isteklerini hiç sesini çıkartmadan kabul ettiği için, hatalı bile olsa, her türü başka her türe dönüştürebiliriz.

Ne yaptığımızı bildiğimizi varsaysak bile, örneğin çoklu kalıtımın kullanıldığı bir durumda yukarıdaki (Fil *) dönüşümü çalışmayabilir.

Bir kere daha: C'den kalan tür dönüşümü işlecini C++'ta kullanmayın.


Ali Çehreli - acehreli@yahoo.com