2012-05-24

C++11の時間ライブラリは美しさを追求したあまり、かえって使いにくくなっているのではないか

C++11の時間関係のライブラリは、非常に美しい設計をしている。

まず、経過時間そのものを表すdurationがある。Cライブラリでいえば、time_tの値の単位を指定するクラスだ。Cライブラリでは、time_tの値は秒であったが、C++では、単位を指定できるのだ。

durationでは、単位ライブラリであるratioを使って、秒、ミリ秒、マイクロ秒などといった時間単位を表現している。

秒
std::chorno::seconds 
ミリ秒
std::chrono::milliseconds
ナノ秒
std::chrono::nanoseconds
時
std::chrono::hours

それ以外の、独自の刻みがほしいとしても、簡単に作成できる。

4分33秒
using four_minutes_thirty_three_seconds = std::chrono::duration< long, std::ratio< 4 * 60 + 33 > > ;

duration同士は、たとえ異なる単位系であっても、足し引きできる。たとえば、10秒と3分を足したら、結果は190秒になってほしい。durationではそういう計算が可能だ。

int main()
{
    std::chrono::seconds sec( 10 ) ;
    std::chrono::minutes min( 3 ) ;

    // 結果の型はstd::chrono::seconds
    auto result = sec + min ;
    
    // 190
    std::cout << result.count() << std::endl ;
}

また、単位時間の変更も非常に楽だ。たとえば、180秒を分に変換したいとする。それには、専用のduration_castが用意されている。

int main()
{
    std::chrono::seconds sec( 180 ) ;
    auto min = std::chrono::duration_cast< std::chrono::minutes >( sec ) ;

    // 3
    std::cout << min.count() << std::endl ;
}

次に、durationを使って、ある起点からの経過時間を表す、time_pointがある。つまり、Cライブラリではtime_tに当たる機能を提供するクラスだ。調べた限りでは、どうやら起点時間がいつであるかは、規格では指定されていない。ただし、起点時間を確かめるポータブルな方法はある。time_pointとtime_tを相互に変換する方法があるので、古き良きCライブラリで調べることができる。C++で新しいライブラリを用意しなかったのはなぜだろうか。時間が足りなかったのだろうか。

// 起点時間を表示するプログラム
int main()
{
    auto tp = std::chrono::system_clock::time_point() ;
    std::time_t t = std::chrono::system_clock::to_time_t( tp ) ;
    std::cout << std::ctime( &t ) << std::endl ;
}

少なくとも、Cライブラリには十分な実績があることは確かだ。

最後に、現在のtime_pointを取得するclockクラスがある。Cライブラリではtime()にあたる関数だ。このクラスは、オブジェクトとしては使わない。ネストされた型名やstaticメンバー関数を使う。クラスは複数あるが、通常は、std::chrono::system_clockを使っておけばよい。より精度の高いクラスとして、std::chrono::high_resolution_clockがある。また、ちょっと特殊なのだが、絶対に時間が逆転しないことが保証されている、std::chorno::steady_clockというものもある。これ以外のクロックは、例えばシステムの日付を過去に戻した場合、以前に返したtime_pointより昔のtime_pointを返す可能性がある。steady_clockは、そのような場合でも一律に進むようになっている。

clockの使い方は簡単で。clock::durationやclock::time_pointが、そのまま、そのclockが使う型になっている。現在のtime_pointは、staticメンバー関数のnowで取得する。

// 美しいC++ライブラリの使い方
auto tp = std::chrono::system_clock::now() ; // 現在時間のtime_pointの取得
auto d = tp.time_since_epoch() ; // durationの取得
auto r = d.count() ; // 経過時間の内部表現の取得

設計自体は美しいと思う。時間という概念を、単位、経過時間、起点からの経過時間というコンセプトに分けて、これらをまとめてクロックというコンセプトにした。

問題は、これらのコンセプトを学ぶのに、労力がかかるという事だ。私は規格を読めるし、Boostや標準化委員会での議論の経緯も知っているからいいものの、通常のユーザーがプログラミング言語をただ使うためだけに、規格や規格制定時の議論や背景の理解を必要とするべきではない。

さて、Cライブラリはどうか。Cライブラリでは、単位を変更することはできない。必ず秒単位である。ただし、単に日付として使いたい場合は、これでも差し支えない。さらに、time_tはtime_tである。これ自体が内部表現であり、何らかの整数型なのだ。time_tはこのまま、整数型として、printfで表示できる。なにもメンバー関数を呼び出す必要はない。メンバーを使うということは、そのクラスについて知っていなければならないという事だ。つまり、ドキュメントをひっくり返して、メンバーの一覧から、望みのメンバーを見付け出さなければならない。これは果てしなく面倒だ。

参考に、Cライブラリで書いてみよう。

// 古き良きCライブラリの使い方
time_t t ;
time( &t ) ;

なんと、これだけのことをするのに、C++では覚えなければならない概念が多すぎる。

第一、そのtime_pointやdurationの型は、テンプレートを使用しているため、非常に長ったらしい型になっている。この記事では、C++11の新機能であるautoを使ったため、読者はそのような恐ろしい型を直接扱わずにすむようになっている。しかし、いざ詳細を学ぼうと思うと、まずテンプレートの詳細を学ばなければならない。なぜならば、クラスはテンプレートの高度な機能を使っており、テンプレートの理解なしでは、本当に理解することができないからだ。

C++11の時間ライブラリは美しく設計されたライブラリである。ユーザーは望みの単位系を使うことができるし、単位系の変換コードを自前で書く必要はない。ましてや、ポインターとは無縁の世界だ。しかし、覚えるべきコンセプト(概念)が多く、また詳細はテンプレートという難しい機能で隠されている。

もちろん、Cライブラリだって、使うとなればドキュメントが必要だ。しかし、Cライブラリのドキュメントは豊富だが、C++のドキュメントは、C++03時代のライブラリでさえ、まれである。多くは、すでに時代遅れで使い物にならない。ましてや、日本語のドキュメントとなると、さらに少ない。時代遅れの英語のドキュメントの翻訳があるだけで感謝しなければならない。

このような状況は、ドキュメントを執筆している私のような人間には、ある程度有利であると言える。世の中には良きドキュメント執筆者が不足している。競争は少ない。規格さえ読めればC++のドキュメントを書くのは難しくない。

しかし、こんなことでいいのだろうか。私には、どうも、美しさを追求した挙句、むしろ学びにくくなっているのではないかと思われてならない。

6 comments:

Unknown said...

x: 4 * 60 * 33
o: 4 * 60 + 30

江添亮 said...

うお。

Anonymous said...

From: Linux Tovalds
To: xxxxx
Subject: Re: [RFC] builin-mailinfo.c をマシな文字列ライブラリを使うようにすること
Date: Thu, 6 Sep 2007 18:50:28 +0100 (BST)
Message-ID:

On Wed, 5 Sep 2007, Dmitry Kakurin wrote:
>
> Git のソースコードを最初に見たとき、ヘンだと思ったこと:
> 1. C++ じゃなくてただの C を使ってる。理由は謎。移植性がどうとか言わないで、
> そんなのウソに決まってるから。

*あんた* のほうこそ大ウソつきだ。

C++ はひどい言語だ。これは、多くの平均以下のプログラマーが使ってるために
さらに輪をかけてゲロゲロになっていて、どうしようもないゴミが
簡単に生産されるようになってる。正直いって、C を選ぶ理由が C++ プログラマーを
追っぱらうため *だけ* だったとしても、それ自体、C を使う強力な理由になりうる。

つまりこういうことだ: C を選ぶのは、唯一のまともな選択だ。以前 Miles Bader が
冗談で「人をムカつかせるため」といっていたが、実はこれは本当だ。
ぼくは C よりも C++ をプロジェクトに使いたがるようなプログラマーは、みな
*本当に* ムカつかせておきたいようなプログラマーだという結論に達した。
そうすれば連中がやってきてぼくの関わっているプロジェクトを
めちゃくちゃにすることがないからね。

C++ はトンでもなく悪い設計の元になりうる。どうせこの言語ではいつも STL やら
Boost やら、その他ゲロゲロベロベロの「素敵な」ライブラリの機能を使って、
それがあんたのプログラムに「役立つ」んだろうが、以下のことが起きる:

- うまく動かないときにもたらされる際限のない苦痛 (あと STL とか、特に Boost が
安定してるとか移植性があるとかいう奴は、どいつもこいつも大ウソつきで、
もはや笑えるレベルを超えている)

- 非効率な抽象プログラミングモデルで、2年たった後にこれらが実はそんなに
効率的じゃなかったことに気づくケース。でもそのときにはすでに全部の
コードがその素晴らしいオブジェクトモデルに依存していて、直すためには
アプリ全体を書き直さなきゃなんない。

言いかえれば、唯一まともで、効率がよくて、システムレベルで使えて、移植性がある
C++ ってのは、基本的に C で使える機能だけに限ったときなんだ。そして C だけに
限定するってことは、他の人がそれをめちゃくちゃにしないってことで、
ついでに沢山のプログラマが実際に低水準の問題を理解することができて、アホらしい
「オブジェクト・モデル」のたわごとを持ちこまないってことだ。

というわけなので、申し訳ないね。git のような、効率が一番の目的のような場合は
C++ の「利点」とやらは単に大間違いなんだよ。ついでにこの事実を理解できない
連中をケトばすことができるってのも、大きなメリットだな。

もし C++ で書かれた VCS が欲しいのなら、Monotone を見てみるといい。
ほんとに。連中は「本物のデータベース」を使っているよ。
「素敵なオブジェクト指向ライブラリ」も使ってる。「ナイスな C++ の抽象化」も
使ってる。そして率直なことろ、一部の情報系の人間が喜びそうな
これらすべての設計上の決定のために、できてきた結果はゲロゲロで
保守不可能なカオスだ。

でもあんたはきっと git よりも気に入るだろう。保証するよ。

Linus

Anonymous said...

「言語仕様になっているべき仕様をテンプレートで実現して延命している」のが原因になっているように思える。

Anonymous said...

正しく理解をして使えばC++のほうがCよりいいんじゃないかな?
理解しないで使ったC++はどこまでも泥沼に入って行ってしまう。

Anonymous said...

まあ正しく理解しているなら、うまく動かないときにもたらされる際限のない苦痛の意味も分かるようになると思うんだけどね。