C++版の文字列split実装の罠
「C++ 文字列 カンマ区切り」とかで検索するとヒットする記事等には、以下のようなコードがよく書かれている。(main関数は動作確認のために自分で付け足したもの)
#include <iostream> #include <string> #include <vector> #include <sstream> using namespace std; vector<string> split(string str, char delim) { vector<string> res; stringstream ss(str); string buf; while (getline(ss, buf, delim)) { res.push_back(buf); } return res; } int main(int argc, char** argv) { string line; while (getline(cin, line)) { vector<string> v = split(line, ','); for (int i = 0; i < v.size(); ++i) { cout << v[i] << endl; } } return EXIT_SUCCESS; }
上のコードでも多くのケースでは問題がないのだろうけど、このコードには以下の欠陥がある。
str
が空文字列だった場合、返されるvector
は空になるstr
をdelim
で区切った最後のトークンが空文字列だった場合、返されるvector
は期待されるサイズより1つ小さくなる
これらはいずれも、残りが空文字列になった時点でwhileループを抜けてしまうからである。まぁ考えれば当然だわな。
なので、ちゃんと文字列分割を実装しようと思ったら、上記のケースを別扱いして対応する必要がある。その対応を入れたコードが以下になる。
#include <iostream> #include <string> #include <vector> #include <sstream> using namespace std; vector<string> split(string str, char delim) { vector<string> res; if (str.length() == 0) { res.push_back(""); } else { stringstream ss(str); string buf; while (getline(ss, buf, delim)) { res.push_back(buf); } if (str[str.length() - 1] == delim) { res.push_back(""); } } return res; } int main(int argc, char** argv) { string line; while (getline(cin, line)) { vector<string> v = split(line, ','); for (int i = 0; i < v.size(); ++i) { cout << v[i] << endl; } } return EXIT_SUCCESS; }
1つ目のケースは、str
が空文字列なのだから、長さは当然0になるので、その判定がtrueになる場合は空文字列1つを入れたvector
を返す。
2つ目のケースは、末尾のトークンが空文字列なのだから、str
の最後の文字がdelim
に等しくなるので、その判定がtrueになる場合は空文字列をvector
の末尾に追加する。