Using a C++ iterator to read a list from a file?

1.4k Views Asked by At

I am trying to read/deserialize a list of elements from a file (and then filter out some of them). It is a useful approach to use an iterator for this purpose?

My current try is

#include <boost/iterator/iterator_adaptor.hpp>
class ReadIterator : public boost::iterator_adaptor<ReadIterator, Elem *, boost::single_pass_traversal_tag>
{
public:
    explicit ReadIterator(const char *filename) : reader(filename)  {}

private:
    friend class boost::iterator_core_access;

    void increment() {
         this->base_reference() = reader.readNext();
    }

    Reader reader;
};

This does not properly deallocate memory (e.g., readNew returns a pointer to a new Elem), what is the right way to do this? Also, how would one actually use such an iterator, how can the end be determined? Or is there a better approach than using an iterator?

3

There are 3 best solutions below

1
On BEST ANSWER

The easy way to do this is to use the std::istream_iterator

std::vector<YourObjectClass>    data;

std::remove_copy_if(std::istream_iterator<YourObjectClass>(file),
                    std::istream_iterator<YourObjectClass>(),
                    std::back_inserter(data),
                    YourFilter
                   );

The standard algorithm copies objects (of type YourObjectClass) from the input file and places them into the vector data if the filter functor returns true.

The only conditions are:

  • YourObjectClass must have an input stream operator
  • YourFilter must overload the operator() for objects of YourObjectClass or be a function that takes a parameter of type YourObjectClass.

Simple Working Example:

  • My object is a line.
  • Filter out line(s) that start with the letter 'A'

Exmpale:

#include <vector>
#include <string>
#include <fstream>
#include <iterator>
#include <algorithm>

struct Line
{
    std::string   data;
};
std::istream& operator>>(std::istream& stream, Line& line)
{
    return std::getline(stream, line.data);
}
struct AFilter
{
    bool operator()(Line const& line) const
    {
        return line.data.size() > 0 && line.data[0] == 'A';
    }
};

int main()
{
    std::ifstream       file("Plop");
    std::vector<Line>   data;

    std::remove_copy_if(std::istream_iterator<Line>(file),
                        std::istream_iterator<Line>(),
                        std::back_inserter(data),
                        AFilter()
                       );
}
0
On

Using an iterator for the purpose is fine. You haven't given any indication that the existing istream_iterator won't work for your purpose though. At least in most cases, you can just write an operator>> for a single element, and use std::istream_iterator to read a list of those elements from the file.

0
On

Rather than readNext() returning a raw pointer to an element, can you construct the call so that it returns a reference-counted smart-pointer that will automatically release it's resources when the reference-count to the pointer goes to zero? Either that, or you're going to have to find a way to fetch the pointer back so you can call delete on it before you allocate more memory via the next call to readNext() when increment() is called again.

As for the "end", what you could do in this case is have some test in your Reader class that detects if you've reached the end of the file or some other ending scenario. If you have, then return false, otherwise return true. For example:

bool increment()
{
    if (reader.not_end_of_file())
    {
        this->base_reference() = reader.readNext();
        return true;
    }

    return false;
}

So now you could call increment() in some type of loop, and you'll know when you've hit the end-of-file or some other ending because the function will return false.