Haftalık C++ 17 – Satır arası değişkenler kod parçası ve tek tanım kuralı

Evet arkadaşlar, QT’ye ilişkin biraz aydınlandıktan sonra, haftalık C++ yazılarımıza, kısa bir kod parçası yazısı ile devam edelim (damarlara C++ zerk etmeye devam :). Bu yazımda C++ 17 ile birlikte gelen Satır Arası Değişkenleri (“inline variables”) kabiliyetinden bahsedeceğim. Bu kabiliyetin de kodunuzu daha okunaklı ve temiz hale getireceğini düşünüyorum açıkçası. Ayrıca sadece başlık dosyalarından oluşan kütüphane geliştiricileri de bu kabiliyeti çok sevecekler. Kabiliyete ilişkin öneriye kaynaklardan ulaşabilirsiniz.

Daha önceki yeni kabiliyet yazılarımda olduğu gibi, yine önce nasıldı, artık nasıl olduğunu sizler ile paylaşacağım.

Normalde (C++ 17 öncesinde), aşağıdaki kodu yazdığınız zaman derleme hatası alırdınız. Çünkü C++, statik olmayan ve const olmayan değişkenlerin bu şekilde tanımlanmasına izin vermiyordu.

Peki bu durumda ne yapıyorduk. Aşağıdaki gibi mi yapıyorduk?

Dikkatli okuyucularım, bu şekilde tanımlanıp, birden fazla dosyada bu başlık dosyası eklendiğinde, bağlama hatası alacağımızı hemen fark ederler. Neden? Çünkü her bir dosya bu tanımlamaları tekrar tekrar yapıyor. Tek tanımlama kuralı ihlal ediliyor (bu kurala yazımın sonunda tekrar değineceğim). Aynı şey aslında bu seviyede yapılan global değişken tanımları için de geçerli, bir diğer ifade ile, başlık dosyalarında tanımladığınız değişkenler, bütün eklenen dosyalar için kopyalanıyor, küçük sabitler için sıkıntı olmayabilir ama büyük sınıflarda nasıl bir sıkıntı olacağını eminim tahmin edersiniz ama o noktaya da geleceğiz, şu an konumuz o değil. 

Bu sıkıntıyı gidermek için ya ilgili değişkeni aşağıdaki gibi “static const” olarak tanımlayabiliriz:

Ya da ilgili değişkenleri herhangi bir Cpp dosyası içerisinde aşağıdaki gibi tanımlamanız gerekmekteydi:

Bu durumda eğer tek başlık dosyalı bir kütüphaneniz olduğunu düşünün, bu değişkeni nereye koyacaksınız? Tek bir başlık dosyası olmasa bile bunun konumlandırılması, açıkçası hem okunabilirliği hem de idameyi zorlaştırıyor. 

C++ 17 ile birlikte gelen “inline variable” kabiliyeti ile birlikte artık yukarıdaki statik üye değişkenleri aşağıdaki gibi ilklendirebilirsiniz.

Ta ta!  Hatırlayacağınız üzere C++ 11 de statik olmayan sınıf değişkenleri için bu tarz ilklendirmeler yapabiliyorduk. Ayrıca C++ 17 ile birlikte constexpr da inline olarak kabul ediliyor. Yani aşağıdaki ifade C++ 17 ile birlikte artık bir tanımlama olarak kabul ediliyor, yukarıdaki örnek ile aynı (deklarasyon değil, çünkü C++ 11 ve C++ 14 de bu kullanım halen geçerli idi ama tanımlama anlamına gelmiyordu):

Aklınıza eminim bunların ne zaman ilklendirildiği sorusu da gelmiştir (gelmemiş ise de önemli bir mevzu). Bu kullanımlarda ilklendirme, ilk kullanım karşılaşıldığında gerçekleştirilmekte.

Buraya kadar anlattığım statik üye değişkenlerin ilklendirilmesinin yanında, “inline variables” ile birlikte, artık başlık dosyalarında tanımlanan global değişkenler de inline olarak tanımlanabilecek. Bu sayede birden fazla nesne oluşturulmasının da önüne geçebileceksiniz (C++ 17 den önce, yukarıda da bahsettiğim üzere bağlama hatası alıyorduk). Başka neyin önüne geçeceğiz? Aslında boşu boşuna daha fazla bellek tüketmenin ve bir çok kez yapıcı (ve sonda yıkıcı) çağrılmasının önüne geçeceğiz (yukarıdaki büyük nesnelerin global olarak tanımlanması mevzusu). Hemen bir örnek ile bu kullanımı da taçlandıralım. Bu arada her ne kadar bazı derleyiciler C++ 17 desteği sunsalar da, bu kabiliyet beklediğimiz gibi çalışmıyor olabilir. Mesela VS 2017′, her ne kadar bu kullanımlar geçerli olarak kabul edilse de, ne yazık ki, beklenen davranış sergilenmiyor, o sebeple lütfen dikkat.

inline anahtar kelimesi ayrıca “thread_local” ile de kullanılabiliyor. Bu sayede her bir thread için eşsiz bir değişken oluşturabiliyorsunuz. Aşağıda tanımlanan değişkenlerin, davranışlarını farklı tanımlamalar ile görebilirsiniz:

Evet “inline variables”‘ın temel işlevlerine baktıktan sonra, şimdi bu konu ile ilintili başka bir konuya da kısaca değinip yazımı sonlandıracağım. Yazımın başında da bahsettiğim bağlama sıkıntısının neden olduğunu veya bunun neye dayandığını bilmek istersiniz diye düşünüyorum.

Bunun dayandığı kural ise Tek Tanım Kuralı’dır (“One Definition Rule”). Bu kural C++ 2003 standardında tanımlanmıştır ve bunun ihlali sonucu bu tarz hatalar alırsınız. Temelde bu kuralın söylediği şey metot ya da sınıfın aynı dosya ya da program içerisinde birden fazla tanımı olmayacağıdır (bulunduğu kapsama göre, bu durum değişkenler, sınıflar, yapılar (struct), fonksiyonlar için de geçerlidir). Zaten çoğu derleyici de bu durum karşısında, bu değişkenin birden fazla tanımlandığına dair size uyarı verecektir. Tabi şunu hatırlatmakta fayda var ki, C++ da deklarasyonlar için herhangi bir yer ayrılmaz, sadece ilgili sınıfa ilişkin nesne oluşturulduğunda yer alma işlemi gerçekleştirilir.

Aşağıda, bu durumu özetleyen basit bir figür görebilirsiniz:

Burada tanımlanan değişkenlerin kapsamı, extern olup/olmaması (harici bağlama yöntemi) gibi faktörleri de göz önünde bulundurmak gerekiyor. Mesela, extern (harici) olmayan farklı dosyadaki nesneler/tanımlamalar (dahili), aynı tip ve isme sahip olsalar dahi farklı olarak değerlendiriliyorlar ve bu sebeple de bağlama hatasına sebebiyet vermiyorlar.

Varsayılan olarak cpp dosyalarınızda tanımladığınız ve const olmayan değişkenlerin hepsi extern, yani harici bağlantısı var, olarak değerlendirilir. Bir diğer ifade ile başka dosyalarda da aynı isimle değişken olursa ODR ihlal edilmiş olur. Bunun non-extern olarak tanımlanması için static anahtar kelimesini kullanabilirsin, ayrıca const global değişkenler de bu şekilde değerlendirilebilir.

Aşağıda olası bütün tanımlamaların olduğu örnek kod parçasını görebilirsiniz:

Bu konu (değişken kapsamları, bağlama yöntemleri, ömrü) biraz daha su götürür ama bence ana fikri verdiğimi düşünüyorum. Merak edenler için bu konulara ilişkin de bir kaç kaynak ekliyorum, onlara da göz atarsanız faydalı olacaktır.

Evet bir yazımızın da daha sonuna gelmiş bulunuyoruz,  bir sonraki yazımda görüşmek dileğiyle.

Kaynaklar

Bir cevap yazın

E-posta hesabınız yayımlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir

This site uses Akismet to reduce spam. Learn how your comment data is processed.