Boost.勉強会 #9 つくばでLTしてきた

発表資料:


10分間のLTのくせに48ページも使いました。
が、ちゃんと10分ぐらいで終わった気がします。ビデオまだ見てないのでわかりませんけど。


で、この資料は10分で説明することを目的にしてて、あまりに簡潔すぎて足りてないところがあるので、それを補足していきます。

第4項 size()で0を調べる代わりにemptyを呼び出そう

empty()とsize()は、効率的には同じになりましたが、だからといって必ずsize()を使いましょうという訳ではないです。
あくまで、empty()とsize()を"効率の観点で"使い分ける必要が無くなったというだけです。
なので、どちらの方がいいかというのは、「空かどうかを調べたい」という意思が強いのか「要素数が0なのかどうかを調べたい」という意思が強いのかあたりで使い分ければいいと思います。

第16項 vectorとstringのデータをレガシーAPIに渡す方法を学ぼう

これは結構物議を醸した感じなので、ちゃんと補足しておきます。

C++03時代のstring

C++03時代では、stringのデータをレガシーAPIに渡す方法はc_str()しかありませんでした。

// こんな関数があったとして...
void doSomething(const char* p) {
  int len = strlen(p);
  // hogefuga
}

// ... C++03 ではこんな風になる。
doSomething(&s[0]); // ダメ
doSomething(&*s.begin()); // ダメ
doSomething(s.data()); // ダメ
doSomething(s.c_str()); // OK

まず s[0]〜s[N-1] までの要素が連続している保証はありません。
そして *s.begin()〜*(s.end()-1) も同様に、要素が連続している保証がありません。
さらに data() は、終端が '\0' で終わってる保証がありません。
ということで、c_str() ぐらいしかレガシーAPIに渡す方法が無かったのです。


また、c_str() の戻り値は const char* なので、

// p に書き込む系の関数
void doSomething2(char* p, int size) {
  for (int i = 0; i < size; i++)
    p[i] = 'c';
}

こんな API があるとき、C++03 の string では、どうやっても渡すことができませんでした。

C++11時代のstring

C++11 ではどちらの問題も解決されています。

doSomething(&s[0]); // OK
doSomething(&*s.begin()); // OK (ただし s.size() != 0 の場合のみ)
doSomething(s.data()); // OK
doSomething(s.c_str()); // OK

s[0]〜s[N-1]までの領域が連続していることが保証されました。begin()〜end()-1も同様です。
data()とc_str()はどちらも同じ仕様になり、'\0'で終わっていることが保証されました。*1


また、領域が連続しているので、

doSomething2(&s[0], s.size());

と書くこともできるようになりました。

emptyチェックいらないの?

最後の

doSomething2(&s[0], s.size());

このコード、s.size() が 0 のときは未定義エラーになるんじゃないの?という話ですが、これは正しいコードです。
s[s.size()] は、CharT()、つまり '\0' を指す参照を返すことが保証されているのです。*2

第40項 ファンクタクラスを変換可能にしよう

unary_function, binary_function が deprecated になったけど、じゃあファンクタ作ったときどうやって引数の型を取ればいいの?となると思いますが、これは、自分で first_argument_type とか second_argument_type とか定義して下さい。
戻り値の型は、decltype なり std::result_of なりを使えば取れるはずです。


わざわざ自分で定義しないといけないなら、そもそも何でこれが deprecated になったの?ということですが、

  • 引数の型とか戻り値の型とか、普段は全然使わないのに、これを継承しなさいという雰囲気が生まれてうざい
  • unary と binary な関数を両方定義する場合に両方継承すると result_type が被ってしまう

とかそんな感じらしいです。

*1:ついでに言えば、c_str() == data() == &s[0] == &*s.begin() であることも保証されています(s.size() != 0 である場合のみ)。

*2:もちろん doSomething2 がちゃんと [p, p+size) の外側に書き込んでないことが前提です。今回の doSomething2 のような処理なら問題なく動作します。