这是制作迭代器的可接受方式吗?

时间:2017-06-28 20:44:06

标签: c++ c++11 for-loop iterator operator-overloading

我喜欢C ++中的for-loop范围,并希望像这样使用它:

#include <bits/stdc++.h>

int main()
{
    for (auto s : LineReader("my-very-big-textfile.txt")) {
        cout << s << endl;
    }
    return 0;
}

这里的目的是迭代一些数据(不先将所有数据读入容器)。在这种情况下,文本字符串是文本文件中的行。但通常它可以是任何东西(包括生成的数据)。

这里LineReader返回一个可迭代的“伪”容器。为了使其工作,for循环需要LineReader对象的迭代器。在C ++中,范围是根据开始和结束迭代器定义的。但是我想使用范围for-loop来迭代数据,其中可能在开始时不知道结尾(例如,在(过大的)文本文件中读取行而不先通过它来查找结尾。)。

所以我这样定义:

免责声明:显示原则的示例代码,因此我不会因过度使用std ::,错误处理,私有/公共关键字等而“纠缠”它...... < / p>

struct ReadLineIterator {
    ifstream ifs;
    string line;

    ReadLineIterator() { }
    ReadLineIterator(string filename) : ifs(filename) { }

    bool operator!=(ReadLineIterator& other) {
        return !ifs.eof();
    }

    ReadLineIterator& operator++() {
        getline(ifs, line, '\n');
        return *this;
    }
    string operator*() {
        return line;
    }
};

struct LineReader
{
    string filename;
    LineReader(const string& filename) : filename(filename) { }

    ReadLineIterator begin()
    {
       return ReadLineIterator(filename);
    }

    ReadLineIterator end() // return a not used dummy iterator since this method must exist
    {
        return ReadLineIterator();
    }
};

当我运行它时,它可以工作。但如果

,我会持怀疑态度
bool operator!=(ReadLineIterator& other) {
    return !ifs.eof();
}

是使该运算符检测序列结束的正确方法。这是因为我没有任何正确的结束对象(end()方法只返回一个虚拟迭代器),并且也没有对它进行比较。而是检查流是否为空。

但我不知道如何以其他方式做到这一点?现在我很高兴这种做法,因为它对我有用,但是知道是否有更好的方法来做同样的事情会很棒。另外很高兴知道这是否适用于所有(C ++)编译器(我正在使用GCC),如果是这样,它适用于未来的C ++标准,迭代器可能会以不同的方式处理。

2 个答案:

答案 0 :(得分:3)

我会分两部分来做。

一个是range类,它只是作为流迭代器的包装器:

template <class T>
class istream_range {
    std::istream_iterator<T> b;
    std::istream_iterator<T> e;
public:
    istream_range(std::istream &is)
        : b(std::istream_iterator<T>(is))
        , e(std::istream_iterator<T>())
    {}

    std::istream_iterator<T> begin() { return b; }
    std::istream_iterator<T> end() { return e; }
};

因此,这允许在基于范围的for循环中使用istream_iterator

for (auto const &s : istream_range<foo>(myfile))
    // do something with s

istream_iterator使用operator>>从指定文件中提取项目,因此第二部分只是一个提取一行的微小类型:

class line {
    std::string data;
public:
    friend std::istream &operator>>(std::istream &is, line &l) {
        std::getline(is, l.data);
        return is;
    }
    operator std::string() const { return data; }    
};

因此,我们的for循环变为类似:

for (auto const &s : istream_range<line>(myfile))
    // do something with s

这样做的明显优势是将两者分离:我们可以使用istream_range<T>来处理T的文件,对于任何T,正常流提取“正确的事情”(包括许多我们目前无法了解的自定义提取器。)

previous question的答案涵盖了一些可能性(包括LineInputIterator似乎更接近你要求的内容。

答案 1 :(得分:1)

标准模板类std::istream_iterator<T>充当迭代器,从istream(带operator>>(istream &, T &))读取连续的T对象,因此您只需要一个从istream读取行的类型T:

class line {
    std::string line;
    friend std::istream &operator>>(std::istream &in, line &l) {
        return std::getline(in, l.line);
    }
public:
    operator std::string() const { return line; }
};

现在让您的LineReader返回std::istream_iterator<line>