C++ Temel İlkeleri (CppCoreGuideLines) endüstride giderek daha fazla kabul görüyor. Bu ilkelere göre statik kod analizi yapan programların ya da eklentilerin sayısı giderek artıyor. Kişisel olarak ben de (birkaç istisna dışında) bu ilkeleri tamamen destekliyorum. Bu yazıda github‘da yayımlanan C++ Temel İlkelerinden enum türlerinin ve sabitlerinin kullanımına ilişkin maddelerin Türkçe çevirileri benim açıklamalarımla birlikte yer alıyor.
Enum.1: Sembolik sabitleri (makroları) kullanmak yerine enum türlerini kullanmayı tercih edin.
Önişlemci programı tarafından ele alınan makrolar kapsam (scope) kurallarına uymazlar ve bir tür kavramına sahip değillerdir. Önişlemci program tarafından kaynak koddan çıkartılan makrolar, hata ayıklayıcı programlar (debugger) ya da benzer araçlar tarafından (tipik olarak) görülmezler. Ayrıca bir makronun farklı değerlere tanımlanması yanlıştır. Sembolik sabit olarak kullanılan makrolarla ilgili yapılan kodlama hatalarının derleyici ya da kontrol araçları tarafından bulunması, saptanması çok zor hatta çoğu zaman olanaksızdır. Oysa enum türleri ve bunlara bağlı olarak enum sabitleri (enumaration constants / enumarators) derleyici tarafından derleme zamanında ele alınır. Derleyiciler ve statik kod analizi yapan programlar kaynak kodda kullanılan enum değerleri için birçok faydalı kontrolü gerçekleştirebilirler. enum türlerinin kullanımı makroların kullanımına göre her zaman daha güvenlidir. Muhtemelen eskiden yazılmış aşağıdaki koda bakalım:
1 2 3 4 5 6 7 8 9 10 11 12 |
// webcolors.h (üçüncü parti bir kütüphaneye ilişkin bir başlık dosyası) #define RED 0xFF0000 #define GREEN 0x00FF00 #define BLUE 0x0000FF // productinfo.h // aşağıdaki makrolar ürün rengine yönelik ayırd edici bir tip bilgisi tanımlıyor #define RED 0 #define PURPLE 1 #define BLUE 2 int webby = BLUE; // webby == 2 (muhtemelen yanlış - istenen bu değil) |
Oysa enum class türlerinin kullanılması bu tür hataları önlediği gibi kodun okunmasını ve bakımını kolaylaştırır:
1 2 3 4 5 |
enum class Web_color {red = 0xFF0000, green = 0x00FF00, blue = 0x0000FF }; enum class Product_info {red = 0, purple = 1, blue = 2 }; //int webby = blue; // geçersiz (sentaks hatası) Web_color webby = Web_color::blue; |
Bu ilkenin özeti şu: enum türlerini kullanabileceğiniz yerlerde asla sembolik sabitleri kullanmayın. Tercihiniz her zaman enum sınıfları olsun. Eğer bir tür kavramından bağımsız olarak isimlendirilmiş tek bir sabite gereksinim duyuyorsanız sembolik sabitler ya da enum sabitleri yerine her zaman constexpr değişkenleri kullanın. C++ dilini yeterince tanımayan C programcıları, C dilinde edindikleri makro kullanma alışkanlıklarını C++ kodlarına da taşıyabiliyorlar. Bundan kesinlikle uzak durmak gerekiyor.
C++ Temel Kodlama İlkelerine göre, kod analizi yapan modern araçların kaynak kodda makro kullanıldığını saptamaları durumunda bir uyarıda bulunmaları öngörülüyor.
Enum.2: Birbiriyle ilişkili isimlendirilmiş sabitlerin temsili için enum türlerini kullanın:
Orijinal metinde bu ilkenin iyi bir şekilde iyi bir şekilde açıklandığını söyleyemeyeceğim. enum sabitleri (enumarators) birbirleriyle ilişkili olmalı ve bir tür olarak temsil edilebilmeli. (fontlar, pencere renkleri, haftanın günleri, aylar vs.)
Örnek:
1 |
enum class Web_color { red = 0xFF0000, green = 0x00FF00, blue = 0x0000FF }; |
enum türünün içerdiği sabitlerin case değerleri olarak kullanıldığı switch deyimleri sıklıkla yazılıyor. Aşağıdaki koda bakalım:
1 2 3 4 5 6 7 8 9 10 11 |
#include <iostream> enum class Product_info { red = 0, purple = 1, blue = 2 }; void print(Product_info inf) { switch (inf) { case Product_info::red: std::cout << "red"; break; case Product_info::purple: std::cout << "purple"; break; } } |
Bu tür switch deyimlerinde enum değerlerinden birinin switch deyiminde kullanılmaması yapılan tipik kodlama hatalarından biri. Bu hata tipik olarak enum türüne daha sonra yeni sabitlerin (enumaratörlerin) eklenmesi durumunda oluşuyor.
Statik kod analizi yapacak programlardan beklentiler:
Eğer bir switch deyimi enum türüne ilişkin sabitlerin çoğınu içeriyor ama bazılarını içermiyor ise uyarı mesajı verilsin.
Eğer bir switch deyiminde bir enum türüne ilişkin enum değerlerinden bazıları kullanılmış ancak default case kullanılmamış ise uyarı verilsin.
Enum.3: Geleneksel enum türleri yerine enum sınıflarını kullanmayı tercih edin
Geleneksel enum türlerinin kullanımında söz konusu olan eksiklikleri ve dezavantajları gidermeye yönelik olarak C++11 standartları ile dile enum sınıfları eklendi.
i) enum sınıflarının kendi kapsamları (scope) var. Böylece farklı enum class türlerine ilişkin enum sabitleri birbiriyle çakışmıyor.
ii) enum sınıfları için baz tür (underlying type) belirtilebiliyor. Böylece enum sınıfları için ön bildirim (forward declaration) yapılabiliyor. (Bu özellik geleneksel enum türlerine de eklendi)
iii) enum class türlerinden aritmetik türlerine otomatik tür dönüşümü (implicit type conversion) yapılmıyor. (Geleneksel enum türlerinden aritmetik türlere otomatik tür dönüşümünün yapılması kodlama hatalarına yol açabiliyor.)
Örnek:
1 2 3 4 5 6 7 8 9 10 |
void Print_color(int color); enum Web_color { red = 0xFF0000, green = 0x00FF00, blue = 0x0000FF }; enum Product_info { Red = 0, Purple = 1, Blue = 2 }; Web_color webby = Web_color::blue; // Aşağıdaki çağrılardan en az biri yanlış Print_color(webby); Print_color(Product_info::Blue); |
Bunun yerine bir enum class kullanılması daha uygun olurdu:
1 2 3 4 5 6 7 8 |
void Print_color(int color); enum class Web_color { red = 0xFF0000, green = 0x00FF00, blue = 0x0000FF }; enum class Product_info { red = 0, purple = 1, blue = 2 }; Web_color webby = Web_color::blue; Print_color(webby); // Error: cannot convert Web_color to int. Print_color(Product_info::Red); // Error: cannot convert Product_info to int. |
Statik kod analizi yapacak programlardan beklentiler:
Geleneksel enum türlerinin kullanılması durumunda uyarı mesajı verilsin.
Enum.4: Güvenli ve kolay kullanım için enum sınıfları için operatör işlevleri yazın:
enum türlerinden değişkenlerin özellikle ++ ve — işleçlerinin operandı olabilmesi sık karşılaşılan bir ihtiyaç. Birçok programcı enum türleri için de operatör işlevlerinin yazılacağını bilmiyor. Oysa enum türleri için tanımlanan operatör işlevleri kullanıcı kodlara büyük kolaylıklar sağlayabiliyor. Örnek:
1 2 3 4 5 6 7 8 9 |
enum Day { mon, tue, wed, thu, fri, sat, sun }; Day& operator++(Day& d) { return d = (d == Day::sun)? Day::mon : static_cast<Day>(static_cast<int>(d)+1); } Day today = Day::sat; Day tomorrow = ++today; |
Yukarıdaki kodda yer alan global operator++ işlevi içinde static_cast tür dönüştürme işlecinin kullanımına dikkat edin. Tür dönüştürme işleci kullanılmasaydı özyinelemeli (recursive) bir çağrı yapılmış olurdu.
Statik kod analizi yapacak programlardan beklentiler:
Tür dönüştürme işleci ile enum türlerine dönüşüm yapılması durumunda uyarı mesajı verilsin.
Enum.5: Tamamı büyük harfler ile oluşturulmuş (all caps) enum sabitleri kullanmayın
Böyle bir isimlendirme sonucunda enum sabitleri ile makrolar arasında isim çakışması oluşabilir.
Kötü kullanım örneği:
1 2 3 4 5 6 7 8 9 10 |
// webcolors.h (üçüncü parti kütüphane başlık dosyası) #define RED 0xFF0000 #define GREEN 0x00FF00 #define BLUE 0x0000FF // productinfo.h // Bu başlık dosyasında ise bir enum sınıfı oluşturulmuş enum class Product_info { RED, PURPLE, BLUE }; // sentaks hatası |
Statik kod analizi yapacak programlardan beklentiler:
Tamamı büyük harf olan (ALL_CAPS) enum sabitlerinin kullanılması durumunda uyarı verilsin.
Enum.6: İsimlendirilmemiş enum türlerinden kaçının:
Eğer bir enum türü için uygun bir isin seçemiyorsanız enum sabitleri birbirleriyle ilişkili değildir. enum türünün kötü kullanımına bir örnek:
1 |
enum { red = 0xFF0000, scale = 4, is_signed = 1 }; |
Yukarıdaki enum türüne bir isim verilmemiş. Çünkü verilebilecek uygun bir isim yok. Sözde bir enum türü oluşturulmuş olmasına karşın, isimlendirilmiş sabitlerin birbirleriyle hiçbir ilgisi olmadığı görülüyor. Burada enum türü kullanmak yerine constexpr değişkenler kullanmak çok daha iyi olurdu:
1 2 3 |
constexpr int red = 0xFF0000; constexpr short scale = 4; constexpr bool is_signed = true; |
Enum.7: Gerekmedikçe bir enum türüne ilişkin baz türü (underlying type) belirtmeyin:
Varsayılan baz türü kullanmak kodun okunmasını ve yazılmasını kolaylaştırır. Varsayılan baz tür “int” tir. Ayrıca varsayılan int türünün kullanılması C dili ile uyumu da sağlar. C dilinde enum türlerinin baz türü int olmak zorundadır.
Örnek:
1 2 3 4 5 6 7 8 9 10 11 12 |
#include <cstdint> enum class Direction : char { n, s, e, w, ne, nw, se, sw }; // belirtilen baz tür daha az bellek alanı kullanılmasını sağlıyor enum class Web_color : int32_t { red = 0xFF0000, green = 0x00FF00, blue = 0x0000FF }; // burada baz türün belirtilmesi gereksiz |
Not: Bir enum türünün ön bildiriminin (forward declaration) yapılması durumunda baz türün belirtilmesi gerekebilir:
1 2 3 4 5 6 7 |
///nec.h enum Flags : char; void f(Flags); //nec.cpp enum flags : char { /* ... */ }; |
Enum.8: Yalnızca gerekli olan durumlarda enumaratör değerlerini belirtin.
enum sabitlerinin varsayılan değerleri kolay kullanım olanağı sağlar. Varsayılan değerlerin kullanılmasıyla, bir enumaratör değerinin birden fazla isimle temsil edilmesi biçimindeki kodlama hatalarından kaçınılır. Varsayılan enum sabiti değerleri (0, 1, 2, …) switch deyimleri için daha etkin bir kod üretilmesine yardımcı olduğu gibi, statik kod analizi yapan programlara daha fazla kontrol olanağı verir.
1 2 3 4 5 6 7 8 9 10 11 12 |
enum class Col1 { red, yellow, blue }; enum class Col2 { red = 1, yellow = 2, blue = 2 }; // yanlışlıkla aynı değer kullanılmış enum class Month { jan = 1, feb, mar, apr, may, jun, jul, august, sep, oct, nov, dec }; // enum değerlerinin 1 ile başlatılması bir konvensiyon enum class Base_flag { dec = 1, oct = dec << 1, hex = dec << 2 }; // bitsel maskeler |
Yukarıdaki kodda enum türü olan Col1‘de varsayılan enum sabiteri kullanılmış.
Col2 türünün bildiriminde yellow ve blue enumaratörleri aynı tamsayı sabitine işaret ediyor. (Yanlışlıkla yazılmış olmalı)
Month türünün bildiriminde enum sabitlerinin, ayların sıra numarasıyla eşleme sağlamak için, jan enum sabiti 1 olarak alınmış. (feb = 2, mar = 3 …). Böyle bir kalıplaşmış kullanmda bir yanlışlık ya da bir sorun söz konusu değil.
Statik kod analizi yapacak programlardan beklentiler:
Birden fazla enum sabiti aynı değerde ise uyarı verilsin.
Birbirini izleyen birer artan enum sabitleriyle (sabitler belirtilerek) oluşturulan enum türleri için uyarı mesajı verilsin.