Gerek akademik/iş hayatımda gerekse kişisel projelerimde ağılıklı olarak kullandığım programlama dili C++. Gerçi artık bir çok güçlü ve istediklerinizi hızlı bir şekilde gerçekleştirilmesine olanak sağlayacak programlama dilleri olsa da (C#, Ruby, Kotlin, Python, Java, vs), ben C++ ı daha çok tercih ediyorum. İlk başlarda ilgilendiğim konularda (2B/3B bilgisayar grafikleri, oyun geliştirme, simülasyon sistemleri, gömülü sistemler) yaygın olarak kullanılması sebebi ile başladı daha sonra da duygusal bir hal aldı. Kısacası seviyorum uleynn 🙂
Bu ve sonraki “Modern C++” etiketli yazılarımda bence her C++ geliştiricisinin bilmesi gereken bir takım yeni özelliklerden bahsedeceğim. Tabi modern derken ne kast ettiğimi merak ediyor olabilirsiniz. Aslında C++ oldukça eski bir programlama dili [Wikipedia]. Tasarımcısı Bjarne Stroustup amca dili ilk olarak 80 lerin başında yayınladı. O zamanlar doktora çalışmaları ile bu işe girişmiş ve daha sonraları C++ ı şekillendirmiştir, halen de aktif olarak çalışmalar da yer alıyor.
İlk başlarda o zamanlar kullanılmakta olan C programlama dilini baz alan ve bu dile sınıf mekanizmalarını getiren (C with Classes olarak ta bilinen) bir dil olarak 1979 da çıktı. 90 larda gerçekleştirilen standartlaştırma ve C++ komitesinin kurulması sonrasında 2003 de, şu anda da oldukça yaygın olarak kullanılan 2003 C++03 (ISO/IEC 14882:2003) oluşturuldu. Sonrasında ise 2011 lere kadar çok ciddi bir güncelleme olmadı ama 2011 den itibaren gayretli çalışmalar sonrasında dil bir metamorfoz geçirerek Modern C++ dediğimiz hali almaya başladı ve sonrasında 2011 (C++0x), 2014 (C++1y) ve nihayet daha dumanı burnunda olan (temmuz 2017 de yayınlandı) 2017 (C++1z) ortaya çıktı. Bunlar ile C++ gerçekten çok güçlü ve bir yönden de bambaşka bir dil haline geldi. İşte modern C++ dediğimde aslında bu son üç yayın ile birlikte gelen kabiliyetlerden ağırlıklı olarak bahsediyor olacağım. Aşağıda aktif olan C++ standartlarını görebilirsiniz:
Yıl | C++ Standardı | İsim |
---|---|---|
1998 | ISO/IEC 14882:1998 | C++98 |
2003 | ISO/IEC 14882:2003 | C++03 |
2011 | ISO/IEC 14882:2011 | C++11 / C++0x |
2014 | ISO/IEC 14882:2014 | C++14 / C++1y |
2017 | ISO/IEC 14882:2017 | C++17 / C++1z |
2020 | to be determined | C++20[15] |
Aşağıda anlatacağımız bütün kabiliyetler C++11 den beri sunulmakta ve muhtemelen piyasadaki aklı başında çoğu C++ derleyicisi ile sunulduğunu düşünüyorum. Ama tabi sizin bunları denemek için bir derleyicisi kurmanıza gerek yok 🙂
https://www.jdoodle.com/online-compiler-c++14 i her zaman kullanabilirsiniz (buna da bir yazı ayırmalıyım 🙂
nullptr
Eğer siz de daha önce C programlama dili kullandı iseniz veyahut C++ pointer aritmetiği ile uğraştı iseniz. NULL pointer ifade etme isteği ve ihtiyacını hissetmişsinizdir. Yani herhangi bir değer içermeme durumu. C ve C++ da bu anlamda her ne kadar 0 kullanılsa da (NULL, Null olarak değişkenlere atansa da) bu aslında sayısal bir değer ve bir pointer tipi değil. Bu çoğu kullanım için sıkıntı yaratmayabilir, fakat problem de olabileceği durumlar olabilmekte 🙂 Örneğin:
1 2 3 4 5 6 |
const int NULL = 0; void function(int input); void function(char* input); // Bu durumda hangi metot sizce cağrılır? function(NULL); |
İlk bakışta ikinci tanımlanan metot çağrılacak gibi görünse de, aslında ilk metot çağrılır. Peki neden? Aslında cevap basit tanımlanan NULL bir tam sayı tipi ve bunu parametre olarak ilk metot.
İlk bakışta ne olacak ki bundan denilebilir veya çok sık karşılaşılacak bir durum olmadığı aşikar fakat bu durum yüzlerce satır kod arasında oluştuğu durumda tespit etmek sıkıntı olabilir (hele ki altında yatan mekanizma bilinmediğinde).
İşte bu tarz durumları ortadan kaldırmak ve pointer da değer atanmamış durumu daha net ifade etmek adına C++ 0x ile birlikte nullptr anahtar kelimesi eklenmiştir. nullptr bütün pointer tanımlamalarında kullanılabilir ve özel bir tip (std::nullptr_t) olarak tanımlanmıştır. Tip bilgisine;
1 |
decltype (nullptr); |
ile elde edilebilir. 0 halen valid bir null pointer değeri olarak dil tarafından kabul edilse de bundan sonra NULL, 0 kullanımına ihtiyaç hissettiğiniz her yerde nullptr kullanınız (ve de kullanmalısınız).
Aşağıda bir takım örnek kullanımları da ekledim:
1 2 3 4 5 6 |
char *pc = nullptr; // TAMAM int *pi = nullptr; // TAMAM bool b = nullptr; // TAMAM. b "false" değerini taşımakta. int i = nullptr; // HATA! foo(nullptr); // foo(char *) çağrılmakta, foo(int) değil |
Enum sınıfları (Type-safe enumerations)
Kısaca enum sınıfları daha önce C++ da kullanılan C-style enum’lerde karşılaşılan aşağıdaki sıkıntıları ortadan kaldırmaktadır:
- Implicit conversion,
- Tanımlanma alanı kirliliği
- “forward declaration” yapamama sıkıntısı
- Enum değerlerine tip/boyut atayamama,
Şimdi gelelim C++0x öncesi enumlara (bu arada eski tip enumler desteklenmeye devam ediyor ama artık kullanmayalım lütfen 🙂 Mevcut C++ enum değerleri aslında alt tarafta bir tam sayı ile ifade edilmektedir. Bu tanımlanan değerler diğer tam sayı veya enum değerleri ile karşılaştırılabilmektedirler. Kodlama açısından bu bir sıkıntı oluşturmamaktadır, sonuçta ikisi de alt tarafta bir sayı. Fakat anlamsal olarak sakıncalı ve alakasız durumlar ortaya çıkabilmektedir (meyve tipi ile 5 i karşılaştırma, veya elma ile sarı renk enum değerlerini karşılaştırma gibi eğer bunların sayısal değerleri aynı ise bunlar aynıdır sonucu alınabilir). Bu sıkıntı aslında başta ifade ettiğimiz 1 no lu sıkıntıları adreslemektedir.
2. duruma gelince; eski tip enumlar ile tanımlanan değerlerin tanımlama alanları sınırlandırılmamıştır. Bu problem Name Collision
olarak ta ifade edilmiştir. Yani aynı isme ait farklı enum tipleri de olsa bunları aynı tanımlama alanında tanımlayamazsınız. Örnek ile daha açık olacak sanırım buyurun 🙂
1 2 3 |
// Aşağıdaki kod derlenmez enum Color {NONE, RED, GREEN, BLUE}; enum Alert {NONE, GREEN, YELLOW, RED}; |
Yeni gelen enum sınıfları ile birlikte yukarıdaki tanımlamalar artık aşağıdaki gibi tanımlanabilir.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
// class kelimesine dikkat enum ile birlikte kullanılacaktır enum class Color {NONE, RED, GREEN, BLUE}; enum class Alert {NONE, GREEN, YELLOW, RED}; // Karşılaştırmalarda kullanım Color currentColor = Color::GREEN; if (Color::RED == currentColor) { // gelen renk kırmızı } // Aynı zamanda fonksiyonlara parametre olarak verilebilir ve bunlara dair // forward declaration yapilabilir enum class Color; // Forward declaration void DrawText(Color currentColor); // Daha sonra ilgili enumun kullanılacağı yerde enum tipi tanımlanır |
Şu an mevcut bir çok derleyici :: ile olan kullanımı desteklese de bu standart değildir. Örnekte ayrıca forward declaration’a da örnek verilmiştir. Bu neden önemli. Bu sayede özellikle sık değişen enumların olması durumunda bütün kodun baştan derlenmesi yerine sadece bunların kullanıldığı cpp kodlarının derlenmesi yeterli olacaktır.
Son olarak, eski enum kullanımlarına ilişkin bir diğer sıkıntı tanımlanacak olan enum değerinin boyutunu belirleyememenizdir. Kaç byte olarak tutulacağı ile ilgili dikte edilen bir standart olmasa da genelde el ile girilen en büyük değeri saklayabilecek büyüklükte olmaktadır. Artık bu belirlenebilmekte ve signed ya da unsigned olan tam sayı tipi bu amaçla kullanılabilir. Varsayılan değer int tir.
1 2 3 4 5 6 7 8 |
// ColorsChar => 1 byte olarak tutulacak enum class ColorsChar : char { RED = 1, GREEN = 2, BLUE = 3 }; // ColorsShort => 2 byte olarak tutulacak enum class ColorsShort : short { RED = 1, GREEN = 2, BLUE = 3 }; // ColorsInt => 4 byte olarak tutulacak enum class ColorsInt : int { RED = 1, GREEN = 2, BLUE = 3 }; |
Range-based döngüler
Standart for döngüleri için “syntactic sugar” olarak sunulan range-based döngüler artık C++ için de kullanılabilecek (c# ve Java gibi dillerde zaten bulunmaktaydı).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
// & kullanımı ile std::array<int, 5> a{ 1, 2, 3, 4, 5 }; for (int& x : a) x *= 2; // a == { 2, 4, 6, 8, 10} => gördüğünüz gibi array in içeriği değişmekte // normal kullanımı ile std::array<int, 5> a{ 1, 2, 3, 4, 5 }; for (int x : a) x *= 2; // a == { 1, 2, 3, 4, 5 } => array in içeriği değişmemekte // Eski C++: for (std::vector<int>::iterator it = array.begin(); it != array.end(); ++it) { std::cout << *it << std::endl; } // Modern C++: for (int item : array) { std::cout << item << std::endl; } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
std::map<std::string, std::vector<int>> map; std::vector<int> v; v.push_back(1); v.push_back(2); v.push_back(3); map["one"] = v; for(const auto& kvp : map) { std::cout << kvp.first << std::endl; for(auto v : kvp.second) { std::cout << v << std::endl; } } |
Range-based döngüler C tipi diziler, initializer list’ler (bunu bir sonraki yazıma bırakıyorum) ve STL de tanımlanan iterator ve begin() / end() metotları tanımlanan sınıflar için kullanılabilir.
auto
Tanımlanan değişken tiplerinin sunulan ilklendirme veri tiplerine göre derleyici tarafından otomatik olarak atanması kabiliyeti de artık C++ a geldi. Çoğu betik dil kullananlar bunu sevecektir 🙂
C++ 0x den önce auto veri tipinin tanımlanma alanı ömrünü belirtmek için varsayılan olarak bütün değişkenlere ekleniyordu. Artık auto veri tipinin yerine kullanılacak ve derleyiciye kardeşim sana zahmet bu tipi otomatik olarak algılar mısın diye rica da bulunacak. Aşağıda çeşitli örnek kullanımlar verilmiştir:
1 2 3 4 5 6 7 8 9 10 11 |
auto a = 3.14; // double auto b = 1; // int auto& c = b; // int& auto d = { 0 }; // std::initializer_list<int> => Bunu da yakında anlatacağım auto&& e = 1; // int&& => Bunu da yakında anlatacağım auto&& f = b; // int& auto g = new auto(123); // int* const auto h = 1; // const int auto i = 1, j = 2, k = 3; // int, int, int auto l = 1, m = true, n = 1.61; // HATA -- `l` int, `m` ise bool. Bunlar aynı olmalı. auto o; // Hata-- `o` nun tipini belirlemek için bir veri girilmemiş |
Açıkçası yukarıdaki tanımlamalar için çok ta büyük bir faydası yok gibi görünse de asıl kullanım kolaylığı karmaşık veri tipi tanımlamalarında ortaya çıkmaktadır örneğin:
1 2 3 4 5 6 7 8 9 10 11 12 |
// Normalde olan kullanım std::vector<int> v = ...; std::vector<int>::const_iterator cit = v.cbegin(); // Yeni kullanım auto cit = v.cbegin(); // Diğer örnekler std::map<std::string, std::vector<int>> map; for(auto it = begin(map); it != end(map); ++it) { } |
Ayrıca C++1y (14) ile birlikte artık fonksiyonların dönüş değerlerinde de auto kullanılabiliyoruz 🙂
1 2 3 4 5 6 7 8 9 10 11 |
// Modern C++: auto get_all_components() const { std::vector<Component*> buffer; for (auto &item : storage_vector) { buffer.push_back(item.second); } return buffer; } |
Performans anlamında herhangi bir ek maliyeti yoktur.
Merhaba,
Range-based döngüler bşliginin 2. kodunda typo gozukuyor.
Öncelikle geri bildirim için teşekkürler. 2. kodtan kastınız neresi, ilk bakışta tam göremedim.