HHeLiBeXの日記 正道編

日々の記憶の記録とメモ‥

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は空になる
  • strdelimで区切った最後のトークンが空文字列だった場合、返される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の末尾に追加する。