> Modern Cpp ile dile eklenen 'attributes' incelemesi: Derleyicilerin belirli durumlarda uyarı mesajı vermesini sağlarlar. Böylece kodlama hatalarına karşı önlem almış oluruz. Bazı durumlarda da bunun tam tersi amaç için kullanılırlar. Yani derleyicinin vereceği uyarı mesajını by-pass etmiş, ilgili uyarının bir 'false-positive' olmasını sağlarız. Bazı durumlarda da derleyicinin daha etkin kod üretmesine olanak vermektedir iş bu 'attributes'. Genel kullanım sentaks biçimi '[[...]]' şeklinde olup, '...' yerine ilgili 'attribute' özelliğini yazıyoruz. 'attribute' ler anahtar sözcük DEĞİLLERDİR. Cpp17 ile birlikte standart olmayan fakat derleyiciye bağlı 'attribute' lar için, diğer derleyicilerin uyarı verme zorunlulu YOKTUR. >> Yıllara göre standartlara eklenen 'attributes': >>> Cpp11 ile dile 'noreturn' ve 'carries_dependency' şeklinde iki adet 'attribute' eklenmiştir. >>>> 'noreturn' : Bir fonksiyonu nitelemekte olup, nitelediği fonksiyonun GERİ DÖNÜŞ YAPMAYACAĞINI ANLATMAKTADIR. Yani kendisini çağıran koda geri dönüş yapılmayacaktır. Böylelikle fonksiyonu çağıran koda geri dönülmesi sırasında üretilen bir takım kodların üretilmemesi sağlanabilir. Bu da kodun daha hızlı çalışmasına olanak verebilir. Geri dönüş değerinin 'void' olması ile karıştırılmasın. Çünkü bir fonksiyon, bir hata nesnesi fırlatarak, 'std::exit()' çağrısıyla, 'std::abort()' çağrısı ile veya C dilindeki standart 'longjmp()' fonksiyon çağrısı ile fonksiyondan çıkış yapabilir. İşte bu 'attribute', aslında sadece bu yöntemler ile fonksiyondan çıkılabileceğini belirtmektedir. Bu 'attribute' ile nitelenmiş bir fonksiyonun 'return' etmesi de bir 'Tanımsız Davranış' oluşturur. * Örnek 1, //.. [[noreturn]] void foo() {} int main() { /* # OUTPUT # */ foo(); // warning: ‘noreturn’ function does return std::cout << "necati..." << std::endl; // İlgili fonksiyon, kendisini çağıran koda geri dönmüştür. Fakat dönmemesi gerekiyor. Bu satırın // aslında 'unreacable code' olması gerekmektedir. } * Örnek 2, //.. [[noreturn]] void foo() { throw std::runtime_error{"ERROR!!!"}; } int main() { /* # OUTPUT # hata yakalandi: ERROR!!! This line should be reachable line of code... */ try { foo(); // This point is 'unreachable' std::cout << "This line should be unreachable line of code..." << std::endl; } catch( const std::exception& ex ) { std::cout << "hata yakalandi: " << ex.what() << std::endl; // This point is 'reachable' std::cout << "This line should be reachable line of code..." << std::endl; } } >>>> 'carries_dependency' : 'multi-thread' uygulamalarda kullanılan, alt seviye kodlarda görülen ama ender kullanılan bir niteleyicidir. >>> Cpp14 ile dile 'deprecated' ve 'deprecated("TheReason")' şeklinde iki adet 'attribute' eklenmiştir. >>>> 'deprecated' : İki farklı kullanım biçimi vardır. İlk kullanım biçimi yalın kullanım biçimi olup, ikincisi ise 'string-literal' ile birlikte kullanım biçimidir. Bu niteleyici ile nitelenen şeylerin üçüncü kişiler tarafından KULLANILMAMASI GEREKTİĞİNİ SÖYLEMEKTEDİR. ER YADA GEÇ İLGİLİ KOD PARÇACIĞI KALDIRILACAKTIR VEYA STANDART OLMAYACAKTIR. Bu niteleyici diğerlerine nazaran daha geniş kodları nitelemektedir. Örneğin, sınıfları, sınıfların üye fonksiyonlarını, global fonksiyonları, 'enum' sabitlerini ve isim alanlarını niteleyebilir. Fakat derleyiciler, bu niteleyici ile nitelenen kodun kullanıldığını gördüğünde bir uyarı mesajı vermesi konusunda TEŞVİK EDİLMEKTEDİR. * Örnek 1, //.. [[deprecated]] void foo() { throw std::runtime_error{"ERROR!!!"}; } int main() { /* # OUTPUT # warning: ‘void foo()’ is deprecated [-Wdeprecated-declarations] hata yakalandi: ERROR!!! This line should be reachable line of code... */ try { foo(); std::cout << "This line should be unreachable line of code..." << std::endl; // This point is 'unreachable' } catch( const std::exception& ex ) { std::cout << "hata yakalandi: " << ex.what() << std::endl; std::cout << "This line should be reachable line of code..." << std::endl; // This point is 'reachable' } } * Örnek 2, //.. struct [[deprecated]] Neco{ int a, b, c; }; int main() { /* # OUTPUT # warning: ‘Neco’ is deprecated [-Wdeprecated-declarations] 32766, 0, 0 */ Neco x; std::cout << x.a << ", " << x.b << ", " << x.c << std::endl; } * Örnek 3, //.. [[deprecated]] int x = 0; [[deprecated]] typedef int Int32; using BYTE [[deprecated]] = unsigned char; struct [[deprecated]] Data { int x, y, z; }; enum Color{ White, Gray, Blue, [[deprecated]] Brown, Black, }; class Nec{ int mx; [[deprecated]] int y; }; int main() { /* # OUTPUT # */ //... } >>> Cpp17 ile dile 'fallthrough', 'maybe_unused' ve 'nodiscard' şeklinde üç adet 'attribute' eklenmiştir. >>>> 'fallthrough' : 'switch-case' merdiveninde 'break' komutu kullanılmadığında uyarı mesajını 'by-pass' etmek için kullanılır. Bu niteleyicinin devamında ';' da gerekmektedir. * Örnek 1, //.. void f1() { std::cout << 1 << std::endl; } void f2() { std::cout << 2 << std::endl; } void f3() { std::cout << 3 << std::endl; } void f4() { std::cout << 4 << std::endl; } int main() { /* # OUTPUT # 1 2 --- 1 2 */ int x = 1; switch(x) { case 1: f1(); case 2: f2(); break; case 3: f3(); break; case 4: f4(); break; } // Yukarıdaki senaryoda 'x' değişkeni '1' değerine sahip olması durumunda hem 'f1()' hem de 'f2()' // fonksiyonu çağrılacaktır. Fakat programcı " 'break;' deyimini mi unuttu yoksa algoritma gereği // orada 'break;' komutu olmaması mı lazım? " sorusunun cevabı kesin olarak bilinemeyeceğinden, // akılda bir şüphe kalmaktadır. std::cout << "---" << std::endl; switch(x) { case 1: f1(); [[fallthrough]]; case 2: f2(); break; case 3: f3(); break; case 4: f4(); break; } // Artık 'x' değişkeninin değeri '1' olması durumunda her iki fonksiyonun çağrılması algoritma gereği // olduğu bilgisini kodu okuyana vermektedir. } >>>> 'nodiscard' : Bir fonksiyonun geri dönüş değerinin 'discard' EDİLMEMESİ GEREKTİĞİNİ, ISKARTAYA ÇIKARTILMAMASI GEREKTİĞİNİ VE MUTLAKA KULLANILMASI GEREKTİĞİNİ ANLATAN bir niteleyicidir. Dolayısla aslen fonksiyonların geri dönüş değerini nitelemektedir. Fakat referans veya gösterici döndüren fonksiyonlarda kullanıldığında bir uyarı mesajı alamayabiliriz. * Örnek 1, //.. [[nodiscard]] bool isPrime(int x) { return true; } int main() { /* # OUTPUT # warning: ignoring return value of ‘bool isPrime(int)’, declared with attribute nodiscard [-Wunused-result] 31 */ int x = 31; isPrime(x); std::cout << x << std::endl; } * Örnek 2, //.. class [[nodiscard]] Neco{}; // note: ‘Neco’ defined here Neco f1() { return Neco{}; } // i. note: in call to ‘Neco f1()’, defined here Neco f2() { return Neco{}; } // ii. note: in call to ‘Neco f1()’, defined here Neco f3() { return Neco{}; } // iii. note: in call to ‘Neco f1()’, defined here int main() { f1(); // i. warning: ignoring returned value of type ‘Neco’, // declared with attribute nodiscard [-Wunused-result] f2(); // ii. warning: ignoring returned value of type ‘Neco’, // declared with attribute nodiscard [-Wunused-result] f3(); // iii. warning: ignoring returned value of type ‘Neco’, // declared with attribute nodiscard [-Wunused-result] } >>>> 'maybe_unused' : Kullanılmayan bir öğe için derleyicinin uyarı mesajı vermesini engeller. * Örnek 1, //.. static void func() { /*...*/ } // Bu fonksiyon çağrılmadığı için derleyici uyarı mesajı verebilir. [[maybe_unused]] static void foo() { /*...*/ } // Bu fonksiyon çağrılmaz ise DERLEYİCİ UYARI MESAJI VERMEYECEKTİR. int main() { int x = 42; // Bu değişken kullanılmadığı için derleyici uyarı mesajı verebilir. [[maybe_unused]] int y = 43; // Bu değişken kullanılmaz ise DERLEYİCİ UYARI MESAJI VERMEYECEKTİR. } * Örnek 2, //.. // #define DebugMode // Bu satırı yorum satırı yaptığımız zaman, // derleyici uyarı mesajı verebilir. #ifdef DebugMode #include #endif static int save_and_project(int, int) { return 0; } std::pair plot_to_curve(int x, int y) { int z = save_and_project(x, y); #ifdef DebugMode assert( z == 0); // 'z' değişkeni sadece 'assert' için kullanıldı. Projemiz 'debug' modundayken bu 'assert' çağrıları // işimize yarayacaktır fakat 'release' moduna geçildiğinde bunları kaldırmamız gerekmektedir. Tek // tek bütün hepsini kaldırmak yerine Koşullu Derleme komutlarını da kullanabiliriz. Dolayısıyla // 'z' değişkeni 'release' modunda artık kullanılmayacaktır. İşte bu durumda derleyici uyarı mesajı // verebilir. Bunu engellemek için de ' [[maybe_unused]] ' isimli niteleyiciyi kullanabiliriz. #endif return { x, y }; } int main() { /* # OUTPUT # 31 / 13 */ auto myPair = plot_to_curve(31, 13); std::cout << myPair.first << " / " << myPair.second << std::endl; } >>> Cpp20 ile dile 'likely', 'unlikely', 'nodiscard("TheReason")' ve 'no_unique_address' şeklinde dört adet 'attribute' eklenmiştir. >>>> 'no_unique_address' : Derleyici, bu nesne için ayrı bir yer ayırmana gerek yok. Sen 'allignment' şeklini ona göre ayarla. * Örnek 1, //.. // EBO : Empty Base Object öncesi. class Empty{ }; class MyclassTwo{ int mx; Empty mex; }; int main() { /* # OUTPUT # sizeof Empty : 1 sizeof int : 4 sizeof Empty : 8 */ Empty ex; std::cout << "sizeof Empty : " << sizeof(ex) << std::endl; int x; std::cout << "sizeof int : " << sizeof(x) << std::endl; MyclassTwo mx; std::cout << "sizeof Empty : " << sizeof(mx) << std::endl; // Çıktıda da görüldüğü üzere boş bir sınıf nesnesi için '1' byte yer kaplamakta. // Derleyici adresleme işlemi yapabilmek için bu ayırmayı yapmak zorunda. // '4' byte için de 'int' türüne ayrıldı. // 'allignment' sağlanması için, 'int' için dört ve 'Empty' için dört byte yer ayırdı. } * Örnek 2, //.. // EBO : Empty Base Object sonrası. class Empty{ }; class MyclassTwo : private Empty{ int mx; }; int main() { /* # OUTPUT # sizeof Empty : 1 sizeof int : 4 sizeof Empty : 4 */ Empty ex; std::cout << "sizeof Empty : " << sizeof(ex) << std::endl; int x; std::cout << "sizeof int : " << sizeof(x) << std::endl; MyclassTwo mx; std::cout << "sizeof Empty : " << sizeof(mx) << std::endl; // Çıktıda da görüldüğü üzere boş bir sınıf nesnesi için '1' byte yer kaplamakta. // Derleyici adresleme işlemi yapabilmek için bu ayırmayı yapmak zorunda. // '4' byte için de 'int' türüne ayrıldı. // 'private' kalıtım kullanıldığı için derleyici 'allignment' yapmadı. } * Örnek 3, //.. // EBO : Empty Base Object, niteleyici kullanarak. class Empty{ }; class MyclassTwo{ int mx; [[no_unique_address]] Empty mex; }; int main() { /* # OUTPUT # sizeof Empty : 1 sizeof int : 4 sizeof Empty : 4 */ Empty ex; std::cout << "sizeof Empty : " << sizeof(ex) << std::endl; int x; std::cout << "sizeof int : " << sizeof(x) << std::endl; MyclassTwo mx; std::cout << "sizeof Empty : " << sizeof(mx) << std::endl; // Çıktıda da görüldüğü üzere boş bir sınıf nesnesi için '1' byte yer kaplamakta. // Derleyici adresleme işlemi yapabilmek için bu ayırmayı yapmak zorunda. // '4' byte için de 'int' türüne ayrıldı. // ' [[no_unique_address]] ' niteleyicisi kullanıldığı için ona göre 'allignment' yapıldı. } >>>> 'likely' ve 'unlikely' : Dallanma deyimlerinde bir kodun yürütülme ihtimalinin daha yüksek veya daha düşük olduğunu belirten niteleyicilerdir. Derleyici bu niteleyici kullandığımız için daha verimli kod üretme zorunluluğu YOKTUR. Dikkatli ve özenli kullanılması gerekmektedir. * Örnek 1, //.. int main() { /* # OUTPUT # */ //... for(size_t i = 0; i < v.size(); ++i) { if( v[i] < 0 ) [[likely]] sum -= sqrt(-v[i]); else sum += sqrt(v[i]); } // Burada biz programın büyük olasılıkla 'if' deyiminin bloğuna gireceğini biliyoruz/tahmin ediyoruz. // Bu bilgiyi de derleyiciye ip ucu olarak geçiyoruz ki daha iyi optimizasyon yapabilsin. } * Örnek 2, //.. int f(int i) { switch(i) { case 1: [[fallthrough]]; [[likely]] case 2: return 1; // 'i' değişkeninin '2' olma ihtimalinin daha yüksek olduğu belirtilmiş. } return 2; } int main() { /* # OUTPUT # 1 */ std::cout << f(2) << std::endl; } * Örnek 3, https://en.cppreference.com/w/cpp/language/attributes/likely //.. #include #include #include #include #include // namespace with_attributes namespace with_attributes { constexpr double pow(double x, long long n) noexcept { if (n > 0) [[likely]] return x * pow(x, n - 1); else [[unlikely]] return 1; } constexpr long long fact(long long n) noexcept { if (n > 1) [[likely]] return n * fact(n - 1); else [[unlikely]] return 1; } constexpr double cos(double x) noexcept { constexpr long long precision{16LL}; double y{}; for (auto n{0LL}; n < precision; n += 2LL) [[likely]] y += pow(x, n) / (n & 2LL ? -fact(n) : fact(n)); return y; } } // namespace no_attributes namespace no_attributes { constexpr double pow(double x, long long n) noexcept { if (n > 0) return x * pow(x, n - 1); else return 1; } constexpr long long fact(long long n) noexcept { if (n > 1) return n * fact(n - 1); else return 1; } constexpr double cos(double x) noexcept { constexpr long long precision{16LL}; double y{}; for (auto n{0LL}; n < precision; n += 2LL) y += pow(x, n) / (n & 2LL ? -fact(n) : fact(n)); return y; } } double gen_random() noexcept { static std::random_device rd; static std::mt19937 gen(rd()); static std::uniform_real_distribution dis(-1.0, 1.0); return dis(gen); } volatile double sink{}; // ensures a side effect int main() { /* # OUTPUT # x = 0.125 0.99219766722932900560039115589461289346218109130859375 0.99219766722932900560039115589461289346218109130859375 equal x = 0.25 0.96891242171064473343022882545483298599720001220703125 0.96891242171064473343022882545483298599720001220703125 equal x = 0.5 0.8775825618903727587394314468838274478912353515625 0.8775825618903727587394314468838274478912353515625 equal x = 1.490116119384765625e-08 0.99999999999999988897769753748434595763683319091796875 0.99999999999999988897769753748434595763683319091796875 equal ----------------------------------------------------------------------------- Time: 4.499906 sec (with attributes) Time: 4.510733 sec (without attributes) Time: 0.733663 sec (std::cos) ----------------------------------------------------------------------------- */ for (const auto x : {0.125, 0.25, 0.5, 1. / (1 << 26)}) { std::cout << std::setprecision(53) << "x = " << x << '\n' << std::cos(x) << '\n' << with_attributes::cos(x) << '\n' << (std::cos(x) == with_attributes::cos(x) ? "equal" : "differ") << '\n'; } std::cout << "---------------------------------------------------------------" << std::endl; auto benchmark = [](auto fun, auto rem) { const auto start = std::chrono::high_resolution_clock::now(); for (auto size{1ULL}; size != 10'000'000ULL; ++size) { sink = fun(gen_random()); } const std::chrono::duration diff = std::chrono::high_resolution_clock::now() - start; std::cout << "Time: " << std::fixed << std::setprecision(6) << diff.count() << " sec " << rem << std::endl; }; benchmark(with_attributes::cos, "(with attributes)"); benchmark(no_attributes::cos, "(without attributes)"); benchmark([](double t) { return std::cos(t); }, "(std::cos)"); std::cout << "\n---------------------------------------------------------------" << std::endl; }