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'; }
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 が被ってしまう
とかそんな感じらしいです。