1.#define Yerine Const, Enum ve Inline Kullanımı

Bu yazı başlığını aslında preprocessor yerine neden compiler kullanılmalıdır olarak değiştirebiliriz.  #define her ne kadar C++ dilinin bir anahtar kelimesi olsa da aslında direk olarak derleyiciye bağlı değildir. 

#define ASPECT_RATIO 1.653 tanımlaması yaptığımızda ASPECT_RATIO ismi derleyici tarafından görülmez. Kod derlenmeden önce önişlemci tarafından silinerek ismin geçtiği yere 1.653 değeri yazılır. Sonuç olarak ASPECT_RATIO sembol tablosuna eklenmemiş olur. ASPECT_RATIO bağlı bir hata durumunda hata mesajı 1.653 değerini referans gösterebilir ve bu durum kafa karışıklığına sebebiyet verebilir. Ayrıca şu durumu da düşünebilirsiniz; sizin yazmadığınız bir başlık dosyasında(.h) ASPECT_RATIO kaynaklı bir hata var ise bahsedildiği gibi hata mesajını da 1.653 referanslı olarak görüyor iseniz hatayı bulmak daha da zor olacaktır. Bu sorun sembolik bir hata ayıklayıcıda da ortaya çıkabilir, çünkü yine programlamakta olduğunuz isim sembol tablosunda olmayabilir.

const double AspectRatio = 1.653;  //Genellikle sabit değişkenler büyük harf ile yazılır
AspectRatio tanımlanması derleyici tarafından görülür ve sembol tablosuna eklenir. Ayrıca sabit float değişken kullanımı #define kullanımından daha ufak kod üretilmesini sağlar. Çünkü #define kullanıldığında her kullanılan yerde kopyalama yapar. AspectRatio kullanıldığın ise tek bir kopya üzerinden çalışma imkanı oluşur.

#define yerine sabit değişken kullanırken, pointer konusunda dikkat edilmesi edilmesi gereken bir nokta vardır. #define NAME “ZAFER” kullanımı yerine sabit değişken kullanmak istersek tanımlama şu şekilde olması gerekir. const char const *Name  = ”ZAFER” iki farklı yerde const tanımlanması yapıldığına dikkat edin. Çünkü amaç kullanıcıya Name sabit değişkenini verdiğimizde bu değişkenin hiçbir değerini değiştirememesi gerekmektedir. Name tanımlamasını char olarak yapmaktansa const std::string Name(“ZAFER”) olarak tanımlayarak daha şık bir kod yazmış oluruz. Bu sayede const pointer kullanımının zorluğundan da kurtulmuş oluruz. Fakat burada şu konuyu düşünmek gerekir. #define NAME “ZAFER” tanımlanması eğer bir başlık dosyasında kullanılması gerekiyor ise bunu nasıl const char const *Name  = ”ZAFER” ya da const std::string Name(“ZAFER”) olarak değiştirebiliriz. Başlık dosyasında bulunan const char const *Name = ”ZAFER” birçok dosyada eklenmiş(include) olacak. Her bir Name, “ZAFER” yazısını taşıyan farklı Name mi olacak yoksa hepsi aynı mı olacak? Sorunun cevabı konunun dağılmaması açısından kısaca her bir Name aynı içeriğe sabit olan farklı Name olacak. Fakat bellekte Tek bir “ZAFER” string adresi olacak. 

Sabit(Const) kullanımındaki diğer konumuz sınıf içerisinde const kullanımı. Bilindiği üzere sınıf içerisinde tanımlanan her eleman sınıf alanı(class scope) kuralına tabidir. Sınıf alanı içinde sabit(const) değişken kullanımı için birkaç yöntem bulunmaktadır. İlk olarak static üye kullanmaktır. Belki static kelimesini görünce bir an hoşunuza gitmemiş olabilir. Fakat farklı kullanım yerlerinde bu static kullanım oldukça faydalı olacaktır. (örneğin iletişim paketi işlerini yapan bir sınıfınızda maksimum paket boyutunu static const olarak belirlemek güzel olabilir. Sonuçta sistemsel bir değer ve sistem içerisinde tek bir tanımlanması olması yeterli.)

 class Message
        {
        private:
                static const int MaxLeng = 1024;
                char msg[MaxLeng];
        };

Yukarıdaki örnekte MaxLeng bildirimi değil tanımlaması yapılmıştır.(bildirim ile tanımlamanın farkına bakınız) C++ sınıf içinde genellikle değişken bildirimleri yapılır. Fakat integers, char s, bool gibi türlerde static const tanımlamaları bu durumun dışındadır. 

#define sınıf içinde kullanıldığında sınıf alanı kuralına uymaz ve bu yüzden sınıf içinde tanımlanan #define sadece sınıf içinde değil tüm dosyada görülür. Bu olumsuz özelliği nedeniyle nerede nasıl kullanılacağını bilemezsiniz. Kullanıcı ilgili sınıf türün bir nesne yaratmadan bile sınıf içindeki makroyu kullanabilir. (kendi elinizle gizli mayın yaratmış olursunuz) Artı özellik olarak makro tanımlamaları bellekte yer kaplamaz. (sadece bir etiket olduğunu hatırlamak gerekir)

Sınıf içinde static const kullanarak #define kullanımından kaçınmış olsakta aslında #define kullanımına göre static const kullanımında eksikler bulunmaktadır. MaxLeng bellekte yer edinmekte ve bunun sonucu olarak bir adresi bulunmakta. Fakat #define bildirimlerinde bellekte yer edinmez ve #define ile belirlenmiş ismin bir adresi bulunmaz. Bu yüzden static const kullanımı yerine enum kullanabiliriz. Bu kullanıma “the enum hack” denilmektedir.
        class Message
        {
        private:
                enum { MaxLeng = 1024};
                char msg[MaxLeng];
        };

Yukarıdaki örnek kullanımda MaxLeng bellekte yer edinmez ve dolayısı ile MaxLeng adres taşımaz. Ayrıca enum sınıf alanı kuralına da dahil olduğu için #define kullanımına göre daha avantajlıdır.

#define üzerinde konuşmuşten fonksiyon görünümlü makrolardan (function like macro) bahsetmemek olmaz. 

// call f with the maximum of a and b
        #define CALL_WITH_MAX(a, b) f((a) > (b) ? (a) : (b))

Yukarıdaki örnek kullanım her ne kadar göze temiz gelse de birçok gizli hataya açıktır. Örneğin aşağıdaki çağrımlarda sonuçlar beklenenden farklı gelecektir.
   
       
        int a = 5, b = 0;
        CALL_WITH_MAX(++a, b);            // a’nın değeri iki kere arttı
        CALL_WITH_MAX(++a, b+10);    // a’nın değeri bir kere artı

Fakat net olarak göze görünmesede yukarıdaki kullanımda gizli bir özellik bize yardım etmektedir. Türden bağımsız işlem. Makronun kullanımında argümanların türü önemsizdir. Argümanlar ister int olsun isterse de double CALL_WITH_MAX makrosu görevini yapabilecektir. (kullanıcı tanımlı tipleri şimdilik göz önüne almayalım). Fonksiyon görünümlü makroların bir avantaj bir de dezavantajını görmüş olduk. C++ ‘da eğer bu dezavantajı  avantaja çevirir ve var olan avantajı da korursak fonksiyon görünümlü makro kullanımından vazgeçebiliriz. Evet çözüm için template inline fonksiyonlar kullanabiliriz. Template yapısı ile türden bağımsızlığı sağlamış oluruz ve fonksiyon özelliği ile de gizli hatalardan(bug) kurtulmuş oluruz.

    template<typename T>
    inline T callWithMax(const T& a, const T& b)
    {
            return (a > b ? a : b);
    }

Bu kullanım ile makroların içindeki parantez kontrolü/kullanımı, argümanların gönderim biçimi(++a durumundaki hata) gibi durumları kontrol etmemize gerek kalmaz. Ayrıca template sınıf içinde tanımlayarak sınıf alanı kuralına da tabi tutabiliriz. 

Konuyu kapatırken kısacası #define yerine enum,  fonksiyon görünümlü makrolar yerine de inline template kullanımına özen göstermek gerekir. 

Yazıyı buradan pdf olarak indirebilirsiniz.

İyi Çalışmalar Zafer Satılmış – 06.01.2020