1

After my recent question, I am trying to implement my own contrived example.
I have a basic structure in place but even after reading this, which is probably the best tutorial I've seen, I'm still very confused. I think I should probably convert the Chapter._text into a stream and for the increment operator do something like

string p = "";
string line;
while ( getline(stream, line) ) {
    p += line;
}
return *p;

but I'm not sure which of the "boilerplate" typedefs to use and how to put all these things together. Thanks much for your help

#include <stdio.h>
#include <stdlib.h>
#include <vector>
#include <iostream>
#include <fstream>
#include <string>

using namespace std;

class Paragraph {
public:
  string _text;

  Paragraph (string text) {
    _text = text;
  }
};

class Chapter {
public:
  string _text;

  /*  // I guess here I should do something like:
  class Iterator : public iterator<input_iterator_tag, Paragraph> {

  }
  // OR should I be somehow delegating from istream_iterator ? */

  Chapter (string txt_file) {
    string line;

    ifstream infile(txt_file.c_str());
    if (!infile.is_open()) {
      cout << "Error opening file " << txt_file << endl;
      exit(0);
    }
    while ( getline(infile, line) ) {
      _text += (line + "\n");
    }
  }

};

int main(int argc, char** argv) {
  Chapter c(argv[1]);

  // want to do something like:
  // for (<Paragraph>::iterator pIt = c.begin(); pIt != c.end(); pIt++) {
  //    Paragraph p(*pIt);
  //    // Do something interesting with p
  // }

  return 0;
}
Community
  • 1
  • 1
confusedCoder
  • 223
  • 1
  • 10
  • How is `Chapter::Iterator` supposed to move around? The only piece of data in your `Chapter` class is the member `_text` which stores the _entire_ content of the input file _sans_ line endings. What's the idea? – Kerrek SB Jun 16 '11 at 01:27
  • Thanks, just corrected the line endings. – confusedCoder Jun 16 '11 at 02:07
  • So... why not just read the entire file into memory? – Kerrek SB Jun 16 '11 at 02:12
  • The general idea is to be able to iterate over Paragraphs extracted from Chapter on the fly STL-style without explicitly storing them in a container. This example is a bit contrived, but it could be useful if the text file was very large and I didn't want to store the whole thing again as a vector, for example – confusedCoder Jun 16 '11 at 02:12
  • Well then... just go and implement the iterator interface! What do you need to know? I guess the magic happens when you press "increment"... will you keep a pointer to the current position or something like that? – Kerrek SB Jun 16 '11 at 02:34
  • @confusedCoder : Have you looked at [`boost::iterator_facade`](http://www.boost.org/doc/libs/release/libs/iterator/doc/iterator_facade.html)? It makes implementing your own iterator **much** less painful. – ildjarn Jun 16 '11 at 04:24

1 Answers1

1

As you weren't planning on a chapter loading at a time (merely a paragraph), and your paragraph is empty, I think this might be best done with a single paragraph_iterator class

class paragraph_iterator : 
public std::iterator<std::input_iterator_tag, std::string>
{
    std::shared_ptr<std::ifstream> _infile; //current file
    std::string _text; //current paragraph
    paragraph_iterator(const paragraph_iterator &b); //not defined, so no copy
    paragraph_iterator &operator=(const paragraph_iterator &b); //not defined, so no copy
    // don't allow copies, because streams aren't copiable. 
    // make sure to always pass by reference
    // unfortunately, this means no stl algorithms either

public:
    paragraph_iterator(string txt_file) :_infile(txt_file.c_str()) {
        if (!infile.is_open())
            throw std::exception("Error opening file ");
        std::getline(_infile, _text);
    }
    paragraph_iterator() {}
    paragraph_iterator &operator++() {
        std::getline(_infile, _text);
        return *this;
    }
    // normally you'd want operator++(int) as well, but that requires making a copy
    // and std::ifstream isn't copiable.
    bool operator==(const paragraph_iterator &b) const {
        if (_infile.bad() == b._infile.bad())
            return true; //all end() and uninitialized iterators equal
        // so we can use paragraph_iterator() as end()
        return false; //since they all are seperate strings, never equal
    }
    bool operator!=(const paragraph_iterator &b) const {return !operator==(b);}
    const std::string &operator*() const { return _text;}
};

int main() {
    paragraph_iterator iter("myfile.txt");
    while(iter != paragraph_iterator()) {
         // dostuff with *iter
    }

}

the stream is encapsulated in the iterator, so that if we have two iterators to the same file, both will get every line. If you have a seperate Chapter class with two iterators, you may run into "threading" problems. This is pretty bare code, and completely untested. I'm sure there's a way to do it with copiable iterators, but far trickier.

In general, an iterator class implementation is closely tied with the data structure it iterates over. Otherwise, we'd just have a few generic iterator classes.

Mooing Duck
  • 64,318
  • 19
  • 100
  • 158
  • Thanks @MooingDuck, I'd solved this problem using a `friend istream& operator>>(istream& i, Paragraph& p)` kind of approach but your solution is really what I was after. In my initial testing of your code, the biggest problem I found was that an uninitialized paragraph_iterator has all the same bit states as actual _infile stream, so the main while loop doesn't go anywhere. I tweaked it to `paragraph_iterator() { _infile.setstate(ios::eofbit); }` and now it seems to work. – confusedCoder Dec 21 '11 at 19:24
  • @confusedCoder: Looking at this code again, you're right, it's got several bugs. The default constructor is wrong, the equivalence operator dereferences a null pointer, there's needless copies of the string in the constructor... but the concept is there. – Mooing Duck Dec 22 '11 at 23:24