[:tr]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!
Kaynaklar
- https://en.cppreference.com/w/cpp/language/scope
- https://www.tutorialspoint.com/cplusplus/cpp_variable_scope.htm
- https://msdn.microsoft.com/en-us/library/b7kfh662.aspx
- http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2016/p0305r0.html
[:en]In this post, we are going to explore a new scoping mechanism which let us to limit scope of a variable further. As you may now, it is always a good practice to reduce the scope of a variable which usually stated as follow so that it is only visible in the intended scope:
Define variables in the smallest scope and as close to the first use as possible.
So why do we do that? Although most modern compilers will warn you about this but you may declare a variable but never use that variable which clutter name space and may hidden other variables (which is also called name hiding or shadowing). This may then increase code complexity and make it less readable and hard to maintain. A very simple and obvious example is provided below:
1 2 3 4 5 6 7 8 9 10 11 | void foo() { int i = 7; // i is not used here if (someCondition) { // i is used only within this block } // i is not used here and not needed! } |
Instead of above usage, following code should be used.
1 2 3 4 5 6 7 8 9 10 | void foo() { // i can not be used here (no need to also) if (someCondition) { int i = 7; // i is used only within this block } // i can not be used here } |
Well, a new mechanism is provided with C++ 17 that will help you to limit the scope of variables which are if and switch with initializers. You can find the corresponding standard page in references section.
Similar to for statements, now you can define initialization statements in if/switch paranthesises that will only applicable to if/else and switch/case blocks, but not outside. Previous usage of if/switch statements are as follow:
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 | // Previous if statement usage: // need to be defined here to be used in if scopes init-statement if (condition) { // Do Something } else { // Do Something else } // C++ 17 way of doing it // init-statements are still exist // but with init statements now we can use following: if (init-statement; condition) { // Do Something } else { // Do Something else } // Variables defined in init statements can not be used here switch (initial-statement; variable) { .... // cases } |
Below code sample illustrates an example usage of this:
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 50 51 52 | //Program to demonstrate init if-else // feature introduced in C++17 #include <iostream> #include <cstdlib> using namespace std; int main() { // Set up rand function to be used // later in program srand(time(NULL)); // Before C++17 int i = 2; if ( i % 2 == 0) cout << i << " is even number" << endl; // After C++17 // if(init-statement; condition) // i name is only limited in if scope if (int i = 4; i % 2 == 0 ) { cout << i << " is even number" << endl; } // Another example about getting input // Before C++17 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 still available here // After C++17 // switch(init;variable) switch (char c (getchar()); 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 is not usable/available here return 0; } |
Another common usage is for container iterators such as below:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | #include <cstddef> #include <map> #include <string> #include <iostream> int main() { // A map container with name and age pair std::map<std::string, size_t> nameCounters { {"Ahmet", 5}, {"Begum", 12}, {"Fatih", 3} }; // Before C++17 auto result = nameCounters.find("Ahmet"); if (result != cend(nameCounters)) std::cout << "Count: " << result->second << std::endl; // result still can be used here and pollute the scope // After C++17 // No redundant pollution :) if (auto result = nameCounters.find("Ahmet"); result != cend(nameCounters)) std::cout << "Count: " << result->second << std::endl; } |
Below code sample also illustrates how can this make your code ugly 🙁
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"; // Before C++ 17 unlimited scope case 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"; // Before C++ 17 limited scope case // additional enclosing scope so 'it' doesn't 'leak' { auto it = myString.find("Hello"); if (it != string::npos) cout << "Hello\n"; } { auto it = myString.find("World"); if (it != string::npos) cout << "World\n"; } // New way of doing it. Which one do you prefer? // C++17 with init if: 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"; } |
This can also be used to limit the scopes of critical sections such as below:
1 2 3 4 5 | // After if lock will be released (unlocked) if (std::lock_guard<std::mutex> lg {my_mutex}; some_condition) { // Do something } |
Finally, it can also be used for APIs that return output parameters as follow:
1 2 3 4 5 | if (DWORD rcCode; ExampleAPI(inputParam, &rcCode)) { cout << "Return code of API was: " << rcCode << endl; } // No useless rcCode variable outside the if-conditional |
There is also another usage but I left it for next week where I am planning to cover another feature of C++ 17, structured binding.
Take care of yourselves!
References
- https://en.cppreference.com/w/cpp/language/scope
- https://www.tutorialspoint.com/cplusplus/cpp_variable_scope.htm
- https://msdn.microsoft.com/en-us/library/b7kfh662.aspx
- http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2016/p0305r0.html
[:]
