なんかついったーで boost::lexical_cast
について盛り上がったので、日記のネタにすることにします:
http://togetter.com/li/2178
lexical_cast
を知らない方の為に少し説明すると、こいつは「文字列表現を媒介にしたキャスト」であり、例えば
int i = boost::lexical_cast<int>("23"); std::string s = boost::lexical_cast<std::string>(42);
のように書けば、i
には数値 23
が、 s
には文字列 "42"
がそれぞれ格納される、というものです。
この例では int
を使いましたが、もちろん int
じゃなくても大丈夫で、 double
や std::complex
といったものから、ボクの考えた文字列クラス oreore::my_string
とかいったものでも普通に変換できます。
その変換可能条件は、ストリームに対する入出力演算が定義されていること。 operator<<
が定義されていれば lexical_cast<Target>(src)
のキャスト元 src
として、 operator>>
が定義されていればキャスト先 Target
として、それぞれ利用することが出来ます。
これは boost::lexical_cast
の実装が、大まかに言って
template<typename Target, typename Source> Target lexical_cast( const Source& s ) { std::stringstream ss; Target t; ss << s; ss >> t; return t; }
という感じになっている(実際には変換失敗時に例外を投げたり wchar_t
に対応したり いろいろと最適化されていたりと、もっと複雑ですが)為。
まー要するに、文字列に/へ変換したい場合にはとりあえず lexical_cast
を使えばよい、ということであり、なんつーかBoostらしい優秀なライブラリなのです・・・が、多少の不満点もあったりするわけです。
そこで今日の随筆では、その(重箱の隅をつつくような)不満点を少し挙げてみようかなぁと思います。
最後までお読みいただければ幸い。
不満点1: 遅い?
とりあえず、以下のコードを実行してみてください(@mickey24氏thx!): http://codepad.org/ib98T2Gd
これは std::sprintf
と boost::lexical_cast
の変換速度を比較するものですが、このコードを動かすと、僕のコンパイラ( Cygwin + gcc 3.4.4 )では boost::lexical_cast
の方が倍以上遅い、という結果になります。
この結果は当然といえば当然で、 lexical_cast
はストリームを使って変換処理を行っているので遅いです(ただし最新のコンパイラでは大差ないか、むしろ lexical_cast
の方が速いという情報もあり、この辺は研究が要るかもしれません)。
もちろん、だからといって lexical_cast
の魅力が減るわけではないですが(最大の魅力は「どんな文字列変換でも同じ表現で書ける」という使い勝手なわけですし)、それでも「遅い」というのはC++使いにとっては死活問題でもある訳で、速いに越したことは有りません。
では、何故遅いのか? それは、最初に書いた大まかな実装でも分かるとおり、ストリームを入力と出力の二回使っているからです。
他はどうあれ、 std::string
に変換する場合には二回目のストリーム出力は要りません( str()
を呼べばいいので)。
幸いにも C++0x の標準ライブラリには、オブジェクトをストリーム経由で文字列変換する to_string
関数が用意されるようなので(個人的には「何故現行のライブラリにこれがないのか」と思います。俺ライブラリでも、これは真っ先に作りました)、恐らく次世代の Boost では string
に変換する場合には to_string
を使うように特殊化された実装を使うようになるのでしょう。
また同様に string
から他のクラスへ変換する場合にも、今度は istringstream
を使った実装に特殊化されることになると思います。Boost 製作者の皆様は最適化大好きみたいですしね。
不満点2: 例外うざい
boost::lexical_cast
では、「文字列→何か」の変換が失敗した場合、例外 boost::bad_lexical_cast
を投げる、という仕様になっています。
が、例外というのはプログラムの実行速度を大きく落とすので、出来ることなら例外を使わずに何とかしたいところ。例えば、事前に例外を投げるかどうかをチェックする、という方法は非常に有効で、多くの例外はこの方法で回避することが出来ます。
しかし lexical_cast
では、事前に例外を投げるかどうかをチェックすることが難しいです。無論、不可能ではないですが、「変換可能かどうかのチェックをするなら、そのときに変換してしまえよ」という感じであり、それは本末転倒。
この問題に対処するには、例えば boost::optional
で値を返す lexical_cast_optional
みたいな関数が存在すると便利です。そこで、俺ライブラリで実装してみようかな・・・と思い立って、 boost::lexical_cast
のソースコードを読んでみたのですが・・・
とてもソースコードが長く、また難しいのです。これでは、とてもじゃないですが自力では実装できない。
そこで僕 SubaruG は、Boost 製作者に以下のような提案をしたいと思います。
- 失敗時に例外を投げるのではなく、
bool
型の戻り値で成功/失敗判定をするlexical_convert
(俺ライブラリにもあります。実装はboost::lexical_cast
+例外処理ですが)を新たに作る。 - さまざまな最適化処理は、全て
lexical_convert
の実装で行うことにする。 boost::lexical_cast
は内部でlexical_convert
を呼び出すのみにして、独自の最適化は行わないようにする。
具体的には、こんな感じで実装するのです(名前空間は俺Boostということで myboost
にしてます):
#include <sstream> namespace myboost { // Boost にこれを用意する // t に s を文字列表現により変換して代入 // 失敗した場合には false を返し、 t は変更されない template<typename Target, typename Source> bool lexical_convert( Target& t, const Source& s ) { // 最適化は全てここで行う // とりあえず最も自然な実装 std::stringstream ss; return ss << s && ss >> t; } // lexical_cast は lexical_convert を用いて実装する // とりあえず bad_lexical_cast の中身は空にしておく // 実際には std::bad_cast から継承されたクラスです struct bad_lexical_cast {}; // 本体 // s を文字列表現を媒介に Target 型へキャストする // 失敗時には bad_lexical_cast 例外を投げる template<typename Target, typename Source> Target lexical_cast( const Source& s ) { // 単純に lexical_convert を用いて実装する Target t; if( !lexical_convert( t, s ) ) { throw bad_lexical_cast(); } return t; } } // namespace myboost
こうすれば、例えば boost::optional
で値を返したいときは、
#include <boost/optional.hpp> #include <boost/utility/in_place_factory.hpp> template<typename Target, typename Source> boost::optional<Target> lexical_cast_optional( const Source& s ) { boost::optional<Target> ret( boost::in_place() ); Target& t = *ret; if( !myboost::lexical_convert( t, s ) ) { return boost::none; } return ret; }
とかやれば作れるわけです(あ、 boost::in_place()
はコピーコスト削減の為に使っているだけなので、意味的には Target()
と同じですよ)。
まぁ、もしこういうものを作ったとしても、実際に高速になるかは分かりません。
というわけで実際に、速度比較してみるとしましょうか:
#include <sstream> namespace myboost { // Boost にこれを用意する template<typename Target, typename Source> bool lexical_convert( Target& t, const Source& s ) { // 最適化は全てここで行う // とりあえず最も自然な実装 std::stringstream ss; return ss << s && ss >> t; } // lexical_cast とかは lexical_convert を用いて実装する struct bad_lexical_cast {}; template<typename Target, typename Source> Target lexical_cast( const Source& s ) { Target t; if( !lexical_convert( t, s ) ) { throw bad_lexical_cast(); } return t; } } // namespace myboost // optional version を定義する #include <boost/optional.hpp> #include <boost/utility/in_place_factory.hpp> template<typename Target, typename Source> boost::optional<Target> lexical_cast_optional( const Source& s ) { boost::optional<Target> ret( boost::in_place() ); Target& t = *ret; if( !myboost::lexical_convert( t, s ) ) { return boost::none; } return ret; } // boost::lexical_cast で文字列を数値に変換する // 失敗時には -1 を返す // boost::lexical_cast version #include <boost/lexical_cast.hpp> int f1( const std::string& s ) { try { return boost::lexical_cast<int>(s); } catch( boost::bad_lexical_cast& ) { return -1; } } // myboost::lexical_cast version int f2( const std::string& s ) { try { return myboost::lexical_cast<int>(s); } catch( myboost::bad_lexical_cast& ) { return -1; } } // optional version int f3( const std::string& s ) { if( boost::optional<int> p = lexical_cast_optional<int>(s) ) { return *p; } else { return -1; } } // テストコード #include <iostream> #include <boost/timer.hpp> #include <boost/format.hpp> using namespace std; using boost::format; void do_test( const string& s, int c ) { cout << format("convert string '%1%' to int %2% times.\n") % s % c; { boost::timer t; for( int i = 0; i < c; ++i ) { f1(s); } cout << format("f1(): %1% sec\n") % t.elapsed(); } { boost::timer t; for( int i = 0; i < c; ++i ) { f2(s); } cout << format("f2(): %1% sec\n") % t.elapsed(); } { boost::timer t; for( int i = 0; i < c; ++i ) { f3(s); } cout << format("f3(): %1% sec\n") % t.elapsed(); } } int main() { do_test( "12345678", 100000 ); cout << endl; do_test( "not int", 100000 ); return 0; }
結果( gcc 3.4.4 最適化オプション -O3
で測定):
convert string '12345678' to int 100000 times. f1(): 0.969 sec f2(): 1.453 sec f3(): 1.563 sec convert string 'not int' to int 100000 times. f1(): 1.703 sec f2(): 1.937 sec f3(): 1.485 sec
結果を見ると、流石にオリジナル boost::lexical_cast
は高速だな、というのが分かります。
しかし、その高速な boost::lexical_cast
でも、変換失敗時には処理速度が ほぼ倍になり、何も最適化していない状態の lexical_cast_optional
に負けてしまう、というのがよく分かるかと思います。
つまり、実際に lexical_cast_optional
は有効なのです!
さらに、ここでもし、 myboost::lexical_cast
がオリジナルと同程度の速度で動くと仮定すると、boost::optional
を使う場合、成功時の処理は一割ほど増加しますが、失敗時に殆ど速度低下しないようにすることが出来る、ということであり、十分にBoostに導入してもいいレベルの部品だと思うのですが、どうでしょう。
とはいえ、このテストの結果から見るに、何の工夫もない状態では圧倒的に boost::lexical_cast
が高速なので、lexical_convert
から俺 lexical_cast
を作るのは無駄でしかない、というのも分かります。
よーするに皆様、車輪の再発明は程々にしましょう、ということで一つ。
あと、誰か、この意見Boostのコミュニティの人に伝えてくれると助かるかもしれないんだよ(他力本願)
追記: アキラさんが lexical_cast_optinal
って別名関数ではなく lexical_cast
って関数名のまま optional
対応版を用意する方法を書いてくれたので、紹介します: http://d.hatena.ne.jp/faith_and_brave/20091225/1261734967