ソーシャルゲームチームのサーバサイドエンジニアの@kazasikiです。
これはカヤックアドベントカレンダーの3日目の記事です。今年もよろしくお願いします。
それでは早速プログラムの話を始めましょう。前置きが長いのでsteady_clockの挙動とかに詳しい方は結論
だけ読んでいただければ幸いです。
まずsteady_clockの紹介
C++にはstd::chrono::steady_clock
というクラスがあります。リファレンスから説明を引用します。
steady_clockは、物理的な時間と同様、決して逆行することがない時間を表現するためのクロックである。
通常はC++で時間を扱う場合はstd::chrono::system_clock
を使用します。ただし、このクラスは時間が不可逆であることを保証しません。実行環境にもよりますが、例えばOSの時間を変えたりしたときに戻ってしまうかもしれません。そうすると、経過時間によって分岐させるような処理が上手く動かなくなります。また、ベンチマークなどで実行時間を計測したい場合も不都合でしょう。通常はOSの時間を変えたりとかしないと思うのであんまり気にすることではないのですが、基本的には現在時刻とは無関係に経過時間を処理に使いたい
という場合はsteady_clockを使う傾向があります。
じゃあsteady_clockを使ってみよう
とりあえず、基本的な実装をしてみましょう。
#include <iostream> #include <chrono> #include "unistd.h" // for sleep using namespace std::chrono; int main() { steady_clock::time_point before = steady_clock::now(); sleep(4); steady_clock::time_point after = steady_clock::now(); seconds sec = duration_cast<seconds>(after - before); std::cout << sec.count() << "秒" << std::endl; }
% g++ -std=c++11 ./test.cpp && ./a.out 4秒
はい。予想通りの結果になりましたね。
steady_clockでも現在時刻を取得したい!けど、その前に
いやーでもデバッグとかログとかで現在時刻をログに吐きたいことってあると思うんですよ。アプリケーション全体的にsteady_clockを使ってるなら出来たらそれをそのままログに吐いたら現在時刻になってほしいなって思うじゃないんですか。
参考までにまずstd::chrono::system_clock
(不可逆を保証しない時計クラス)の出力方法を示します。
#include <iostream> #include <chrono> #include <ctime> #include <iomanip> using std::chrono::system_clock; int main() { system_clock::time_point p = system_clock::now(); // steady_clockと同様に現在時刻を取得 time_t t = system_clock::to_time_t(p); // time_t型に変換 const tm* lt = std::localtime(&t); // それをlocaltimeで日本のタイムロケールに変換 std::cout << std::put_time(lt, "%c") << std::endl; // この工程が既に面倒くさい }
% g++ -std=c++11 ./test.cpp && ./a.out Mon Nov 27 10:43:49 2017
このコードを読んで勘の良い方ならお気づきかと思いますが、time_pointをtime_t型に変換するメソッドto_time_t
はsystem_clockのメソッドです。そしてその、steady_clock版はもちろん存在しません。ということは、そもそもsteady_clockのtime_pointは標準の時刻型に変換できません。(少なくともsteady_clockクラスにはそういったメソッドはありません)
でも待って!僕達にはtime_point::time_since_epochがついてる!
がっかりするのはまだ早い!time_pointにはtime_since_epochというメソッドが生えてます。試しにsystem_clockに対して使ってみましょう。
#include <iostream> #include <chrono> using namespace std::chrono; int main() { system_clock::time_point now = system_clock::now(); seconds s = duration_cast<seconds>(now.time_since_epoch()); std::cout << s.count() << std::endl; }
% g++ -std=c++11 ./test.cpp && ./a.out 1511747519
1511747519
はUNIXタイムで2017/11/27 10:51:59
です。わーい、大丈夫そう!
では、同じことをsteady_clockにもやってみます。
#include <iostream> #include <chrono> using namespace std::chrono; int main() { steady_clock::time_point now = steady_clock::now(); seconds s = duration_cast<seconds>(now.time_since_epoch()); std::cout << s.count() << std::endl; }
% g++ -std=c++11 ./test.cpp && ./a.out 509
あるぇー?めっちゃ小さい値出てきた(・3・)
さて、ここからが問題で、steady_clock::time_point::time_since_epoch
は何の値を出しているのでしょう?
調べればすぐに分かるのですが、これはマシンの起動時刻から経過時間を出力しています。time_since_epochだけどUnix Epoch Timeとは言ってないって感じです。
uptimeでOSの起動からの経過時間を出力させると一目瞭然です。
% g++ -std=c++11 ./test.cpp && ./a.out && uptime 547 10:54:15 up 9 min, 1 user, load average: 0.04, 0.04, 0.02
9min = 540secで544なのでまぁ大体あってますね!
結論
とはいえ、私たちはsteady_clock::time_point
をどうしても現在時間にしたいわけです。そこで便利な方法はこれです。
#include <iostream> #include <chrono> #include <ctime> #include <iomanip> #include "unistd.h" // for sleep using namespace std::chrono; // system_clockを表示する関数 void print_time(const system_clock::time_point &p) { time_t t = system_clock::to_time_t(p); const tm* lt = std::localtime(&t); std::cout << std::put_time(lt, "%c") << std::endl; } int main() { // 最初にsystem_clockとsteady_clockの両方で現在時間を取得 system_clock::time_point start1 = system_clock::now(); steady_clock::time_point start2 = steady_clock::now(); ::print_time(start1); // 動作確認のため出力 sleep(4); steady_clock::time_point now = steady_clock::now(); // 開始時のsteady_clock::nowとの差をとって、開始時のsystem_clock::nowに足す ::print_time(start1 + duration_cast<seconds>(now - start2)); }
% g++ -std=c++11 ./test.cpp && ./a.out Mon Nov 27 10:58:28 2017 Mon Nov 27 10:58:32 2017
アプリケーションの開始時に1回だけsystem_clock::nowとsteady_clock::nowを取得しておけば、あとはsteady_clock::nowを使うだけで現在時間が出力できます。毎回system_clock::nowをとって出力すればいいのではとも思いますが、毎回system_clock::nowとsteady_clock::nowのどっちも取得するのはいやだなぁと言うときは使えると思います。(コストの面も気になりますが細かい検証はしてません)
というわけで、steady_clockをメインでつかってるアプリケーションで現在時刻を表示することが出来ました。 めでたし。めでたし。
明日12/4はおかむーの「Qiitaで1000いいねを稼ごうとした人の末路」です!