mercredi 28 juin 2017

Is this an acceptable way of making an iterator?

I like the range for-loop in C++, and want to use it like this:

#include <bits/stdc++.h>

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

The purpose here is to iterate through some data (without reading all into a container first). In this case text strings which are lines in a text file. But generally it could be anything (including generated data).

Here LineReader returns an iterable "pseudo"-container. And in order for that to work, the for loop needs iterators from the LineReader object. In C++ the range is defined in terms of a begin and end iterator. But I want to use the range for-loop to iterate through data where the end might not be known at start (for example reading line for line in a (excessively big) text file without going through it first to find the end.).

So I define that like this:

Disclaimer: Example code showing the principle, so therefore I'm not "pestering" it with excessive use of std::, error handling, private/public keywords and so on...

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();
    }
};

When I run this, it works. But I'm skeptical if

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

is a proper way to make this operator to detect the end of the sequence. This because I don't have any proper end object (the end() method just returns a dummy iterator) and comparisons aren't done against it either. Instead I check if the stream is empty.

But I don't see how I could do this in any other way? For now I'm happy with this way of doing it since it works for me, but it would be great to know if there is better ways of doing the same. Also it would be nice to know if this works with all (C++) compilers (I'm using GCC) and if so that it works with future C++ standards where iterators might be handled differently.

Aucun commentaire:

Enregistrer un commentaire