1.Const Kullanımı

C dilinden alışık olduğumuz const anahtar sözcüğü C++ dilinde biraz daha güçlendirilmiş ve etki alanı geliştirilmiştir. İlk olarak C dilinde nerelerde const kullanabildiğimizi hatırlayalım. Fonksiyon parametrelerinde, global çözünürlükte(scope), local çözünürlükte const anahtar sözcüğünü kullanabiliriz. İkinci olarak hatırlanması gereken nokta ise const anahtar sözcüğünün pointerler ile beraber kullanılması.

İşte bu noktada C++ da const anahtar kelimesini kullanımı daha da önemli ve avantajlı olmaktadır. C++ avantajlarına geçmeden önce pointer ile const kullanımını hatırlayalım. Eğer const anahtar kelimesi gösterici işaretinin(asterisk)(*) sol tarafında kullanılır ise göstericinin gösterdiği değişkene yazma/atama işlemi yapamayız ve böylece içeriği korumuş oluruz. Eğer const anahtar kelimesi gösterici işaretinin(asterisk)(*) sağ tarafında kullanılır ise göstericinin göstermiş olduğu adres değiştirilemez, böylece gösterici tek bir adrese bağlı kalır.

    int y = 666;
    int x = 0;

    const int *lp;
    lp = &x;
    *lp = 8;  //atama işlemi hata
    lp = &y;  //başarılı

    int * const rp = &x;
    *rp = 35;   //doğru
    rp = &y;   //adres atama/değiştirme hatası

Yukarıdaki örnekte const int *lp; yazımı, int const *lp; biçiminde de yazılabilir. Genellikle ilk kullanım karşımıza çıksa da ikinci kullanıma da gözümüz aşina olmalı.

STL iteratorleri göstericiler(pointer) üzerinden modellenmiştir. Bu yüzden sabit bir tip göstericisi yerine T* biçiminde karşımıza çıkar. Template değişkenlerde const anahtar sözcüğünün gösterici işaretinin(asterisk) sağ ve sol tarafında kullanımına eş kullanımlar vardır. Aşağıda örnek kullanımlar bulunmaktadır. Sağ taraf kullanımı için const_iterator tip tanımlaması(typedef) kullanılmıştır.

    std::vector<int> vec;
    const std::vector<int>::iterator iter = vec.begin();
    *iter = 66;
    ++iter;
    
    std::vector<int>::const_iterator cIter =
    vec.begin();
    *cIter = 35;

Değişkenler ile const anahtar kelimesinin kullanılması daha çok fonksiyon parametrelerinde karşımıza çıkar. Fonksiyon parametrelerinde kullanılması, yazım amacının yanında okunabilirliği de arttırmaktır.

class Rational { ... };
const Rational operator*(const Rational& lhs, const Rational& rhs);

Yukarıda bildirimi verilen fonksiyon parametlerinin const olması ile kullanıcıya fonksiyon çağrısında verilen argümanların üzerinde yazma işlemi yapılmayacağı bildirerek kodun okunabilirliğini arttırmış oluruz. Ayrıca fonksiyonu geliştiren kişinin de bu değişkenlere bir değer yazmaya kalktığında derleme zamanında hata alarak kodun doğru yazılmasını sağlamış oluruz. 

Yukarıdaki açıklamalar pek yabancı olmadığımız konular olsada çoğu kullanıcı fonksiyon geri dönüşlerinin const biçimini göz ardı ediyor. Fonksiyon geri dönüş değerini const yaparak R-value üretmiş oluruz ve böylece kullanıcı bu geri dönüş değerini sadece okuma amaçlı kullanabilir. Bu noktada bu tam bir gereklilik değilmiş gibi görülebilir. Kod yazan kişi birazda kendi dikkatli olması gerekiyor diyebilirsiniz. Fakat aşağıdaki kullanımlara bakarsanız aslında const kullanımının hayat kurtarabildiğini görebilirsiniz.
(a * b) = c;  .Bu satır amacına göre doğru ya da yanlış olabilir. Amaç yanlış ise ve (a*b) işleminin sonucu const değil ise ne derleyici ne (bağlayıcı)linker ne de başka birisi bu satırın yanlış olduğunu söyleyebilir.
if (a * b = c) … durumunda da kişi sınama yapmak isterken hata yapmış olabilir.(belki de kasıtlı kullanım) Peki bu durumun hata olduğunu a*b işleminin sonucu const olmadığında kim söyler. Tabiki kimse, kod hatasız derlenir.
Yukarıdaki hatalar eğer büyük bir projede olmuş olsaydı bulması kesinlikle zor ve yorucu olacaktı. Fakat fonksiyonun geri dönüş değeri const olduğu durumda bu hatalar derleme zamanında önümüze gelecekti. 

2.const Üye Fonksiyonları

Bu başlık altında C dilinde görmediğimiz Cont üye fonksiyonların kullanımını göreceğiz. Bu başlık altında ilerlemeden önce ufak bir ön bilgi eklemek istiyorum. C dilinde 2 farklı çözünürlük alanında fonksiyon yazılabilir, local(static) ve global çözünürlükte(scope) fonksiyon yazılabilir. C++ da bu kuralların yanında sınıf içinde de fonksiyon yazılabilir.(class scope) ve bu tür fonksiyonlara üye fonksiyon ya da sınıfın metodu denilmekte. 

Yukarıda belirtilen fonksiyon türlerinden sadece sınıf içinde yazılan üye fonksiyonlar const fonksiyon olabilir. Tüm üye fonksiyonlar gibi Const üye fonksiyonlar da içinde bulunduğu sınıfın arayüzünü ya da işlev özelliğini anlatır. Dolayısı const anahtar sözcüğü üye fonksiyonlara ilk olarak okunabilirlik özelliği katmaktadır. Kullanıcı sınıfın const üye fonksiyonunu kullandığında herhangi bir değişkenin değişmeyeceğini bilir. Bu en önemli kazançlardan biridir.

class myTest
{
public:
    void foo(int x) const
    {
             std::cout << "num: " << x << std::endl;
    }
}

Yukarıda örnek bir const üye fonksiyon tanımlaması bulunmaktadır. Eğer ki üye fonksiyonun tanımlaması sınıfın içinde yapılmayacak ise tanımlamasının yapıldığı yerde de const anahtar kelimesi kullanılmalır. 

class myTest
{
public:
    void foo(int x) const;
};

void myTest::foo(int x) const
{
    std::cout << "num: " << x << std::endl;
}

Diğer fayda ise const sınıflar ile çalışmasıdır. Bir sınıf türünden nesne oluşturulurken cont olarak oluşturulması o sınıfın tüm üye değişkenlerini de const yapar.(not üye fonksiyonları değil, üye değişkenleri) Dolayısı ile sınıfın hiçbir değişkeni üzerinde değişiklik yapamayız. C++ bu durumda kullanıcıya cont sınıfın değişkenlerinin değişmeyeceği garantisini vermek için sınıfın const olmayan üye fonksiyonlarının çağrılmasını engeller. Bu açıklamaları daha iyi anlamak için aşağıdaki örneğe bakalım.

class myTest
{
public:
    void foo(int x) const
    {
               std::cout << "num: " << x << std::endl;
    }

    void func(void)
    {
                std::cout <<" func " << std::endl;
    }
         int x;
};
int main()
{
    const myTest my;

    my.foo(5);
    my.func();  // hatalı kullanım. 
              my.x = 88;   // hatalı kullanım. 
}

Yukarıdaki örnekte myTest sınıfı türünden const my nesnesi tanımlanmış. foo() üye fonksiyonu const olduğu için çağrımı doğrudur. Fakat func() üye fonksiyonu const olmadığı için çağrım yanlıştır ve derleme zamanında hata alınır.(fonksiyon içinde atama işlemini olup olmaması öenmli değil). Diğer bir hata ise  my.x = 88; atamasıdır. my nesnesi const olduğu için üye değişkenine atama yapılamaz.

Peki elinizde bir const olan ve olmayan aynı sınıf türünden iki nesne var ve ikisini de aktif olarak kullanıyorsunuz. Ya da const olan ve olmayan nesneler için aynı hizmeti vermek istiyorsunuz. Örneğin elinizde yazı(string) tutan bir sınıfınız olsun ve bu sınıf içinde yazının(string) istenilen sıradaki karakterini döndüren üye fonksiyonumuz olsun. Dolayısı ile hem const hem de const olmayan türdeki nesneler için bu isteği yerine getirmemiz gerekir.

class TextBlock 
{
Public:           
       const char& operator[](std::size_t position) const
       { return text[position]; }
           
       char& operator[](std::size_t position)
       { return text[position]; }

private:
       std::string text;
};

Yukarıdaki durum için TextBlock sınıfını oluşturalım. Fark ettiyseniz operator[] üye fonksiyonunun iki farklı tanımlaması var. Yukarıdaki örnek için bu şekilde çözüm oluşturabiliriz. const olarak oluşturulmuş yazı nesnesinin istenilen karakterine ulaşılmak istendiğinde const char& operator[](std::size_t position) const üye fonksiyonu çağrılmış olur. Const olmayan nesne için ise aynı işlem char& operator[](std::size_t position) ile yapılır. Burada şuna da dikkat etmek gerekir const nesneler için const char& operator[](std::size_t position) const çağrımının başarılı olması için sadece const tanımlanması değil geri dönüş değerinin de const olması gerekir. 

Derleyici const olarak yaratılmış nesnenin hiçbir şekilde değişikliğe uğramayacağını garanti altına alabilir mi? Yukarıdaki örnekte derleyici yukarıdaki const fonksiyonun geri dönüş değerinin de const olması gerektiğini nesnenin const olmasından mı, yoksa üye fonksiyonun const olmasından mı anladı? Bu soruların cevabı üzerinde bir düşünelim. Çünkü cevap sonrasında değinilmesi gereken bir başka konu oluşacak.

Derleyici const olarak yaratılmış nesnenin hiçbir şekilde değişikliğe uğramayacağı garanti altına alamaz. İşte bu noktada yeni konu olarak fiziksel değişmezlik (bitwise constness (also known as physical constness)) ve mantıksal değişmezlik(logical constness) karşımıza çıkar. Fiziksel değişmezlik const üye fonksiyonların içinde bir değişiklik olmayacağı garantisini verir. Fakat mantıksal değişmezlik ise yazılımcının elindedir. Konuyu daha iyi anlamak için aşağıdaki örneği inceleyebilirsiniz.

class CTextBlock 
{
public:
   char& operator[](std::size_t position) const  //uygunsuz, mantıksal değişmezlik hatası
   { return pText[position]; }

private:
    char *pText;
};

3.const Delikleri

const üye fonksiyonlar içinde sınıfın değişkenlerinin değiştirilemeyeceğini gördük. Fakat C++ kendi kuralını kontrollü olarak çiğnenmesini sağlayabiliyor. mutable anahtar kelimesini kullanarak const üye fonksiyonlar içinde istenilen değişkeni değiştirebiliriz. İşte bu kullanıma ben const delikleri diyorum. Her ne kadar kulağa faydalı bir özellik gibi gelse de bu kullanımdan kaçınmalıyız. En son çare noktasında kullanılmalıdır. Çünkü üye fonksiyonlar sınıfın arayüzü/vitrini oluşturur ve kullanıcı bir fonksiyonun bildiriminde const gördüğünde onun const olma özelliğine güvenir. Fakat arka taraftaki işlerin farklı olması kullanıcıları üzebilir. 

class CTextBlock
{
public:
    std::size_t length() const;
private:
    char *pText;
    mutable std::size_t textLength;
    mutable bool lengthIsValid;
};

std::size_t CTextBlock::length() const
{
    if (!lengthIsValid)
    {
        textLength = std::strlen(pText);
        lengthIsValid = true;
    }

    return textLength;
}

4.Const ve Const Olmayan Üye İşlevlerin Çiftlenme Hatası

const  Üye Fonksiyonları başlığında verdiğimiz class TextBlock sınıfının operator[] fonksiyonları doğru yazılmadı. Amaç const nesne olduğunda const üye fonksiyonun çağrılması, const olmayan nesneler için const olmayan üye fonksiyonun çağrılmasını sağlamaktı. Farkettiyseniz üye fonksiyonların görevi aynı. Bu da bize içinde aynı kodların olacağını/olabileceğini gösteriyor ve karşımıza kod tekrarı durumunu oluşturuyor. Aşağıdaki örnek sınıfı incelersek problem olan kod tekrarını daha iyi anlayabiliriz.

class TextBlock
{
public:
    const char& operator[](std::size_t position) const
    {
              // do bounds checking
             // log access data
             // verify data integrity
             return text[position];
    }

    char& operator[](std::size_t position)
    {
             // do bounds checking
             // log access data
             // verify data integrity
             return text[position];
    }
private:
    std::string text;
};

Problemin ilk çözümü olarak aklımıza const üye fonksiyon içinde const olmayan üye fonksiyonu çağırıp kod tekrarından kurtulmak gelebilir. Fakat bu çözüm çok daha büyük hata içerir. Çünkü const olmayan üye işlev içindeki değişlik nedeniyle const fonksiyon kullanma kuralını çiğnemiş oluruz. Çözüm olarak ise tam tersi bir yol izlemeliyiz. Const olmyan üye işlem içinde const olan üye işlevi çağırarak problemi çözebiliriz.

    const char& operator[](std::size_t position) const
    {
             // do bounds checking
             // log access data
            // verify data integrity
             return text[position];
    }

char& operator[](std::size_t position)
{
    return const_cast<char&>(static_cast<const TextBlock&>(*this)[position]);    
}


5. Son

Const anahtar kelimesi doğru kullanıldığında çok faydası görülür. Çoğu hatanın derleme zamanında yakalanmasını sağlar ve hatanın maliyet oluşturmasını engeller. Kodun okunabilirliğini arttırarak kodu kullanacak olan müşterilerin işini kolaylaşltırı. Böylece kod kalitesi artar. Sonuç olarak mümkün olan her yerde const kullanılması gerektiğini söyleyebiliriz. 

Pdf olarak indirmek için bu linki kullanabilirsiniz.

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