【问题标题】:Is this an acceptable way of making an iterator?这是制作迭代器的可接受方式吗?
【发布时间】:2017-12-02 09:26:51
【问题描述】:

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

#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 循环来遍历开始时可能不知道结尾的数据(例如,在(过大的)文本文件中逐行读取而不先通过它来找到结尾。)。

所以我这样定义:

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

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++ 标准。

【问题讨论】:

  • 当我阅读您的问题的其余部分时,让我开始说,永远不要包含 bits/ 文件:它们是非标准且不可移植的。改为包含所需的标准标题。
  • 不,这不是实现迭代器的正确方法,因为it != it 通常会返回true
  • 那不是一个有效的迭代器。您必须拥有 typedef 和一些其他运算符。此外,正如您所注意到的,operator!= 不会检查 other 迭代器。请参阅here 了解如何制作合适的迭代器。看起来你想要一个input_iterator
  • 必读:std::istream_iterator。特别是:“默认构造的std::istream_iterator被称为流尾迭代器。当一个有效的std::istream_iterator到达底层流的末尾时,它就等于流尾迭代器。”跨度>
  • @AlexanderHuszagh 恐怕这实际上在 C++17 中已被弃用。参见例如 stackoverflow.com/questions/37031805/…stackoverflow.com/questions/43268146/…

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


【解决方案1】:

我会分两部分来做。

一个是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_iterators:

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

一个istream_iterator使用operator&gt;&gt;从指定文件中提取项目,所以第二部分只是一个提取一行的小类型:

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&lt;T&gt; 来处理T 的文件,对于任何T,正常的流提取都会做“正确的事情”(包括许多自定义提取器)我们目前无法知晓)。

previous question 的答案涵盖了更多可能性(包括似乎更接近您要求的LineInputIterator)。

【讨论】:

  • 我添加的 istream_range 初始值设定项列表中似乎缺少一些右括号,然后该解决方案起作用并实现了我想要的。自然,在将 s 输入 std::cout 之前,我必须将 s 显式转换为 std::string。 非常感谢您的解释。
  • @VisitorIterator:感谢您的错误报告——我相信我已经纠正了错别字。很高兴它有帮助。
【解决方案2】:

标准模板类 std::istream_iterator&lt;T&gt; 充当从 istream 读取连续 T 对象的迭代器(使用 operator&gt;&gt;(istream &amp;, T &amp;)),因此您只需要一个从 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&lt;line&gt;

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2012-07-30
    • 2010-09-15
    • 2013-09-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-02-16
    相关资源
    最近更新 更多