Evet, sevgili yazılımperver dostlarım, C++ yazılarımıza hız kesmeden devam ediyoruz (evet hızımız 1-2 ppm [post per month] olsa da). C++ 11 STL kütüphanesi ile gelen, önceki bir çok yazımda değindiğim ve muhtemelen C++ 11’e ilişkin değineceğim son önemli kabiliyetlerden biri olan ve uzun süredir sizler ile paylaşmayı planladığım std::chrono kütüphanesini sayfamıza konuk ediyoruz. Bu sefer diğer yazılarımdan farklı olarak, kütüphane ve ilgili kabiliyeti tek bir yazıda aktarmaktansa, bir kaç yazıda sizler ile paylaşmayı planlıyorum. Bu yazı biraz girizgah, temel kavramlarda giriş olacak ve bu kavramlardan birisini masaya yatıracağız. Sonraki yazılarımda da, kalan kavramlara daha yakında bakıyor olacağız.
Her ne kadar, C++ 11’den önce de POSIX uyumlu kütüphaneler sunuluyor olsa da, bunlar standart bir API/arayüz üzerinden sunulmuyorlardı ve genelde ilgili işletim sistemi ve platforma göre de farklılıklar gösterebiliyordu. C++ 11 ile gelen std::chrono kütüphanesinin bizlere kazandırdığı en önemli özellik, bu kabiliyetlerin derlenerek standart bir şekilde sunulması oldu diyebiliriz. Ayrıca, geliştirilen bir çok uygulamada da kullandığımız için de çok önemli bir kabiliyet olduğunu değerlendiriyorum.
Bu kütüphanenin geliştirilmesinde göz önünde bulundurulan bir diğer husus da, zamanlayıcı ve saatlerin farklı sistemlerde farklı çözünürlük ve hassasiyet sunabilecekleridir ki. Özellikle gömülü ve gerçek zamanlı yazılım geliştirenlerinizin, bu konuyu yakinen tecrübe ettiklerini düşünüyorum. Bu noktada, STL geliştiricileri, bu hususları kotarabilmek adına üç önemli kavram geliştirmişlerdir. Bunlar: süre (“duration”), zaman noktası (“time point“) ve saat (“clock“)’tir. Bu yazımda ilk kavram olan, süreyi detaylı bir şekilde sizler ile paylaşacağım. Zaman noktası, belirli bir referans zamandan geçen süreyi ifade eder. Saat’ler ise sistemlerden ilgili referans noktasını ve geçen süreyi almak için kullanılacak olan nesnelerdir.
Aşağıda verilen figür, bu ilişkiyi zannımca gayet iyi göstermektedir:
Bu kütüphaneyi kullanmak için <chrono> başlık dosyasını eklemeniz yeterlidir. Ayrıca çoğu sınıf ve fonksiyonlarda std::chrono adres uzayı içerisinde yer alır (olaki adres uzayını belirtmezsem ve hata alırsanız diye diyorum).
Şimdi, bu yazımızın yakışıklısına bakalım.
Süre (“Duration”)
Bu kütüphane içerisinde belirli bir zaman biriminde tanımlı olan bir süre/zaman aralığını ifade etmek için kullanılan sınıftır. Bu birimler neler olabilir? Saniye, dakika, saat gibi bilindik süre birimleri olabileceği gibi sizlerin tanımlayacağı süre birimleri de olabilir. İlgili birime göre süre değişir ve bunlar arasında dönüşüm yapılabilir. Burada önemli olan bu sürenin bir adet/miktar bilgisi (“tick”) bilgisi içermesidir. Peki bunu nasıl tanımlıyoruz? Hemen referans sayfasında verilen bilgileri inceleylim. Öncelikle sınıf tanımına bakalım:
template< class Rep, class Period = std::ratio<1> > class duration;
göreceğiniz üzere şablon bir sınıf ve iki temel şablon parametresi kullanılıyor: “Rep” ve “Period”. Amaç, bu iki parametre ile süre merfhumunu ifade etmek.
- Rep: İlgili süreye ilişkin adet bilgisini ifade edecek aritmetik veri tipini ifade eder ki genelde bu bir tam sayı (int) olur ama zorunlu da değildir (yani float ya da double da olabilir),
- Period: İlgili sürenin saniye cinsinden karşılığını ifade eder. Bir diğer deyiş ile saniyede kaç adetinin olduğunu ifade eden bir oranı temsil eder. Mealen 🙂 kaç saniye ile bu süreyi ifade ederim. Ör. 1 sn için bu oran 1’dir. Yukarıda da verdiğim sayfada ifade edildiği gibi bu bilgi sadece dönüşümlerde kullanılmaktadır.
- Eminim burada kullanılan std::ratio sınıfı dikkatinizi çekmiştir. Bu sınıf da, C++ 11 ile gelen ve derleme zamanın rasyonel sayıları ifade etmek için kullanılabilecek bir mekanizma sunan sınıftır. Bu sınıfın tanımı şu şekilde: ‘template<std::intmax_t Num, std::intmax_t Denom = 1> class ratio;’, burada Num, pay’ı ve Denom da paydayı ifade eder.
Yukarıdaki ifade ile temel süreleri ya da kendi tanımlamalarınızı aşağıdaki gibi tanımlayabilirsiniz:
1 2 3 4 5 6 7 8 9 |
// Ornek bazi sure tanimlamalari using miliSaniye = std::chrono::duration<int, std::ratio<1, 1000>>; using dakika = std::chrono::duration<int, std::ratio<60>>; // Saniyeyi de adet yerini bulsun diye ekleyelim :) using saniye = std::chrono::duration<int, std::ratio<1>>; // Ari kusu saniyede 200 adete kadar kanat cirpabilir using ariKusuKanatCirpma = std::chrono::duration<int, std::ratio<1, 200>>; |
İyi de sevgili yazılımperver, bu süreleri her uygulamam için, hele de temel süre tipleri için her seferinde tanımlamalı mıyım? Elbette hayır, kütüphane içerisinde bilindik tipler için bunlar tanımlı.
Peki hangileri ve bunların tipi ne? Hemen, onları da sizler ile paylaşayım:
Süre Birimi | Tip | Tanım |
Nano saniye | std::chrono::nanoseconds | std::chrono::duration</*signed integer type of at least 64 bits*/, std::nano> |
Mikro saniye | std::chrono::microseconds | std::chrono::duration</*signed integer type of at least 55 bits*/, std::micro> |
Mili saniye | std::chrono::milliseconds | std::chrono::duration</*signed integer type of at least 45 bits*/, std::milli> |
Saniye | std::chrono::seconds | std::chrono::duration</*signed integer type of at least 35 bits*/> |
Dakika | std::chrono::minutes | std::chrono::duration</*signed integer type of at least 29 bits*/, std::ratio<60>> |
Saat | std::chrono::hours | std::chrono::duration</*signed integer type of at least 23 bits*/, std::ratio<3600>> |
Ayrıca, buraya şunu da ekleyelim ki C++ 20 ile birlikte, gün, hafta, ay ve yıl da eklenmiştir. Sevgili yazılımperver dostlarım, burada şu konuya da yeri gelmişken değineyim. Bazen, C++ 11 veya daha sonra dile eklenen özelliklere, yeni C++ standartları ile de eklemeler olabilmektedir. Bu sebeple bunları kullanırken, elinizin altındaki derleyiciye ve “cppreference” gibi sayfalara başvurmayı unutmayın.
Gelelim bu “duration” sınıfı ile sunulan API’lere.
Bunlardan en önemlisi, “count()” API’si. Bu API kullanarak, ilgili süreyi ifade eden sayısal adeti elde edebilirsiniz. Hemen bir örnek ile bunu inceleyelim:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
#include <chrono> #include <iostream> int main() { // 3 milisaniye std::chrono::milliseconds ms{3}; // Yukarıdaki milisaniye süresini aritmetik bir operasyon ile mikrosaniye olarak tutalım std::chrono::microseconds us = 2*ms; // Saniyenin kayan sayılar ile ifade edilmesi std::chrono::duration<double, std::ratio<1>> fractalSecond(3.5); std::cout << "3 ms süresine ilişkin adet: " << ms.count() << " adet\n" << "6000 mikrosaniye süresine ilişkin adet: " << us.count() << " adet\n" << "Kayan sayı ile süre adetinin ifade edilmesi: " << fractalSecond.count() << " adet\n"; } |
Bunların yanında, bu sınıf ile birlikte, yukarıdaki örnekte de görebileceğiniz üzere, süreler ile temel aritmetik işlemler ya da karşılaştırma işlemleri de yapabilirsiniz.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
#include <chrono> #include <iostream> int main() { // Farklı süre birimlerini birbiri ile karşılaştırabilirsiniz if(std::chrono::seconds(2) == std::chrono::milliseconds(2000)) std::cout << "2 s == 2000 ms\n"; else std::cout << "2 s != 2000 ms\n"; // Farklı süre birimlerini birbiri ile karşılaştırabilirsiniz if(std::chrono::seconds(61) > std::chrono::minutes(1)) std::cout << "61 s > 1 min\n"; else std::cout << "61 s <= 1 min\n"; // Ön tanımlı chrono tiplerini kullanmak için bu adres uzayını ekleyebilirsiniz using namespace std::chrono_literals; static_assert(1h == 60min); static_assert(1min == 60s); return 0; } |
Yukarıdaki örneklerde de görebileceğiniz üzere bir takım dolaylı dönüşümler gerçekleştirilebilmektedir. Burada, önemli olan bunun yönüdür. Dönüşüm yönü, daha detaylı birime yönelik olursa bir sıkıntı yok. Yani saniyeyi, milisaniyeye dolaylı bir şekilde sıkıntısız çevirebilirsiniz. Fakat tersi durum, her zaman mümkün olmayabilir ve bilgi/keskinlik kaybedebiliriz. Hemen bir örnek ile buna bakalım:
1 2 3 4 |
std::chrono::seconds sec(30); // Aşağıdaki dönüşüm otomatik olarak yapılamaz ve hataya sebebiyet verir std::chrono::minutes m1 = sec; |
Peki bu durumlarda ne yapacağız? Bu tarz dönüşümler için de “std::duration_cast” ‘i kullanıyoruz. Nasıl mı hemen bakalım.
1 2 3 4 |
std::chrono::seconds sec(30); // Aşağıdaki dönüşüm artık hata vermez. std::chrono::minutes m1 = std::chrono::duration_cast<std::chrono::minutes>(sec); |
Bu dönüşümler özellikle ifade edilen süreler için kullanılan aritmetik veri tipleri farklı ise daha önemli bir hal alır.
Şimdi, bu noktaya kadar gördüğümüz kabiliyetlere ilişkin bir örnek kodu inceleyelim. Bu kod parçası ile amacımız verilen bir süreyi, saat, dakika ve saniye’lere ayırmak:
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 |
#include <chrono> #include <iostream> #include <iomanip> using namespace std::chrono_literals; int main() { // Elimizdeki süre bilgisi std::chrono::milliseconds ms{7285042}; // Şimdi bunu saat, dakika ve saniyeye ayıralım // Dikkat edelim, burada bu milisaniye cinsinden verilen süre, // saat cinsine dönüştürülürken kırpılıyor, yuvarlanmıyor! std::chrono::hours hours = duration_cast<std::chrono::hours>(ms); std::chrono::minutes minutes = duration_cast<std::chrono::minutes>(ms % chrono::hours{1} ); std::chrono::seconds seconds = duration_cast<std::chrono::seconds>(ms % chrono::minutes{1} ); std::chrono::milliseconds mseconds = duration_cast<std::chrono::milliseconds>(ms % chrono::seconds{1} ); // Şimdi bu süreyi bastıralım std::cout << "[" << setfill('0') << setw(2) << hours.count() << "::" << setfill('0') << setw(2) << minutes.count() << "::" << setfill('0') << setw(2) << seconds.count() << "::" << setfill('0') << setw(2) << mseconds.count() <<"]"; return 0; } |
Burada önemli olan iki nokta: dönüşümlerde yuvarlama değil, kırpma yapılmasıdır (Tavan ya da taban ya da yuvarlama için C++ 17 ile bir takım API’ler de sunulduğunu burada ifade etmekte fayda var). Modülo operatörünün de bu süre değişkenleri ile kullanılabilmesinin ne kadar büyük kolaylık sağlaması.
Yukarıdaki API’ler yanında, statik olan aşağıdaki API’ler de sunulmaktadır:
- zero(): 0 saniye döner,
- min/max(): minimum ve maksimum süre bilgilerini döner.
Son olarak C++14 ile gelen, kullanıcı tanımlı değişmezler de mevcut. Bu da ne demek diyorsanız, ilerlemeden önce aşağıdaki yazıma bir göz atabilirsiniz 😉
Haftalık C++ 34 Kullanıcı Tanımlı Değişmezler
Yanlız bunları kullanmak için “std::literals::chrono_literals” adres uzayını ortama eklemeli ya da tiplerin önüne eklemelisiniz. Bunun ile birlikte aşağıdaki kullanımlar mümkün olabilmekte:
Kullanıcı Tanımlı Chrono Değişmezleri | |
Saat ifade eden std::chrono::duration değişmezi | |
Saat ifade eden std::chrono::duration değişmezi | |
Saat ifade eden std::chrono::duration değişmezi | |
Saat ifade eden std::chrono::duration değişmezi | |
Saat ifade eden std::chrono::duration değişmezi | |
Saat ifade eden std::chrono::duration değişmezi |
Bunlara ilişkin de basit bir örneğe bakalım:
1 2 3 4 5 6 7 8 9 10 11 |
#include <iostream> #include <chrono> int main() { using namespace std::chrono_literals; auto day = 24h; auto halfhour = 0.5h; std::cout << "Bir gün " << day.count() << " saattir\n" << "Yarım saat " << halfhour.count() << " saattir\n"; } |
Evet dostlar, std::chrono kütüphanesine ilişkin, std::duration sınıfına detaylı bir bakış attık. Bu konu ile ilgili kaynaklar kısmına da bir sayfa ekliyorum. Vakti olanlar oraya da bakabilirler. Bir sonraki yazımda görüşmek dileğiyle bol kodlu günler diliyorum.