Bu yazımda, C++ 17 ile gelen ve değişkenlerin tanımlı olduğu kapsamları kısıtlamamıza yardımcı olacak yeni bir kapsam mekanizmasından bahsedeceğim. Bildiğiniz gibi, tanımladığınız değişkenlerin kapsamlarını olabildiğince küçük olacak (yani geçerli olduğu kapsamı azaltmak) şekilde tanımlamak tavsiye edilen bir yöntemdir.
Değişkenleri olabildiğince küçük bir kapsam içerisinde ve ilk kullanıma yakın tanımlayın.
Peki neden? Her ne kadar çoğu derleyici siz değişkeni tanımlayıp kullanmadığınızda sizi uyarsa da, her bir değişken tanımladığı kapsamı kirletmekte ve daha iç kapsamlardaki aynı isimli değişkenleri örtebilir (buna ayrıca gölgeleme veya isim gizleme de denir). Bundan daha da önemlisi bu tarz tanımlamalar kodun karmaşıklığını arttırmakta ve onu daha az okunur, idamesi zor bir hale getirebilir. Aşağıda bu durumu gösteren çok basit bir örneği görebilirsiniz:
1 2 3 4 5 6 7 8 9 10 11 |
void foo() { int i = 7; // i bu kapsamda kullanılmıyor if (someCondition) { // i sadece bu if bloğu içinde kullanılıyor } // i burada kullanılmıyor ve ihtiyaç duyulmuyor! } |
Yukarıdaki kullanım yerine, aşağıdaki kod kullanılmalıdır.
1 2 3 4 5 6 7 8 9 10 |
void foo() { // i nin burada kullanılmasına ihtiyaç yok o zaman burada tanımlamayalım if (someCondition) { int i = 7; // i sadece bu if bloğu içinde kullanılıyor } // i burada kullanılmıyor ve ihtiyaç duyulmuyor! } |
C++ 17 ile birlikte bu tarz kapsam kısıtlamayı kolaylaştıran “ilklendirmeli if/switch ifadeleri” mekanizması dile eklendi. Aşağıdaki standarda ilişkin kaynakta bu mekanizmaya ilişkin detayları bulabilirsiniz.
For ifadelerine benzer şekilde, if/switch parantezleri içerisinde sadece bu ifadelere ilişkin bloklar (if/else, switch/case) içerisinde geçerli olacak ilklendirmeleri tanımlayabiliyorsunuz. Aşağıda geleneksel ve yeni yaklaşımı görebilirsiniz:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
// Onceki if ifadesi kullanımı: // Bu ilklendirmeler burada yapılıyordu ce if blokları sonrasında da ulaşılabilirdi init-statement if (condition) { // Bir şeyler yapalım } else { // Ya da başka şeyler yapalım } // C++ 17 yaklaşımı // ilklendirmeler halen var ama if ifadesi parantezi içerisinde taşınıyor // ve if/else blokları dışında ulaşılabilir değiller if (init-statement; condition) { // Bir şeyler yapalım } else { // Ya da başka şeyler yapalım } // Benzer şekilde switch ifadesi için de ilgili ilklendirmeler yapılabilir switch (initial-statement; variable) { .... // cases } |
Bu mekanizmaya ilişkin başka bir örnek kullanıma bakalım hele:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 |
#include <iostream> #include <cstdlib> using namespace std; int main() { // Rastgele sayı üretelim hele bir srand(time(NULL)); // C++17 öncesi int i = 2; if ( i % 2 == 0) cout << i << " bir cift sayi" << endl; // C++17 sonrasi // if(init-statement; condition) // i ismi sadece if blogu icinde ulasilabilir if (int i = 4; i % 2 == 0 ) { cout << i << " bir cift sayi" << endl; } // Baska bir ornek // C++17 oncesi char c = getchar(); switch (c) { case 'a': move_left(); break; case 's': move_back(); break; case 'w': move_fwd(); break; case 'd': move_right(); break; case 'q': quit_game(); break; } // c degiskeni halen ulasilabilir // C++17 sonrasi // switch(init;variable) switch (char c2 (getchar()); c2) { case 'a': move_left(); break; case 's': move_back(); break; case 'w': move_fwd(); break; case 'd': move_right(); break; case 'q': quit_game(); break; } // c2 artik ulasilabilir değil return 0; } |
Bu mekanizmanın bir diğer yaygın kullanımı konteynerler ile kullanılan iteratörler ile oluyor:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
#include <cstddef> #include <map> #include <string> #include <iostream> using namespace std; int main() { // Yaş ve isim cifti map<string, size_t> nameCounters { {"Ahmet", 5}, {"Begum", 12}, {"Fatih", 3} }; // C++17 Oncesi auto result = nameCounters.find("Ahmet"); if (result != cend(nameCounters)) cout << "Adet: " << result->second << endl; // result halen ulaşılabilir ve kapsamı kirletiyor // C++17 sonrasi // Kirletme artik yok :) if (auto result = nameCounters.find("Ahmet"); result != cend(nameCounters)) cout << "Adet: " << result->second << endl; } |
Aşağıda da normalde çok çirkinleşebilecek bir kodun nasıl sade bir hale gelebileceğini göreceksiniz:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
#include <iostream> #include <string> using namespace std; int main() { const string myString = "Hello World"; // C++ 17 oncesi limitlenmemis kapsam durumu auto it = myString.find("Hello"); if (it != string::npos) cout << it << " Hello\n"; auto it2 = myString.find("World"); if (it2 != string::npos) cout << it2 << " World\n"; // C++ 17 oncesi limitlenmis kapsam durumu // ekstra küme işareti ile it diger kapsamlara açılmıyor ama kod ta pek güzel olmadı sanki { auto it = myString.find("Hello"); if (it != string::npos) cout << "Hello\n"; } { auto it = myString.find("World"); if (it != string::npos) cout << "World\n"; } // C++17 sonrası // Sizce hangisi daha güzel :) if (const auto it = myString.find("Hello"); it != string::npos) cout << it << " Hello\n"; if (const auto it = myString.find("World"); it != string::npos) cout << it << " World\n"; } |
Bu mekanizma ayrıca kritik alanlarda kilitleme için de kullanılabilir:
1 2 3 4 5 |
// If blogu sonra kilit otomatik olarak serbest bırakılacak if (std::lock_guard<std::mutex> lg {my_mutex}; some_condition) { // Do something } |
Son olarak, özellikle çıktı parametreleri içeren API’lere için de bu mekanizma aşağıdaki gibi kullanılabilir:
1 2 3 4 5 |
if (DWORD rcCode; ExampleAPI(inputParam, &rcCode)) { cout << "API donus degeri: " << rcCode << endl; } // rcCode if blogu dısında ulasılabilir degil |
Bu mekanizmaya ilişkin bir diğer kullanım daha var ama onu da inşallah yeni bir C++ 17 özelliği olan “structured binding” ‘i aktaracağım haftalık C++ yazıma saklıyorum.
O vakte kadar, kendinize iyi bakın!