Выбрать главу

    return !(*this == rv);

  }

};

#endif // TOKENITERATOR_H ///:~

The TokenIterator class derives from the std::iterator template. It might appear that some kind of functionality comes with std::iterator, but it is purely a way of tagging an iterator so that a container that uses it knows what it’s capable of. Here, you can see input_iterator_tag as the iterator_category template argument—this tells anyone who asks that a TokenIterator only has the capabilities of an input iterator and cannot be used with algorithms requiring more sophisticated iterators. Apart from the tagging, std::iterator doesn’t do anything beyond providing several useful type definitions, which means you must design all the other functionality in yourself.

The TokenIterator class may look a little strange at first, because the first constructor requires both a "begin" and an "end" iterator as arguments, along with the predicate. Remember, this is a "wrapper" iterator that has no idea how to tell whether it’s at the end of its input source, so the ending iterator is necessary in the first constructor. The reason for the second (default) constructor is that the STL algorithms (and any algorithms you write) need a TokenIterator sentinel to be the past-the-end value. Since all the information necessary to see if the TokenIterator has reached the end of its input is collected in the first constructor, this second constructor creates a TokenIterator that is merely used as a placeholder in algorithms.

The core of the behavior happens in operator++. This erases the current value of word using string::resize( ) and then finds the first character that satisfies the predicate (thus discovering the beginning of the new token) using find_if( ) (from the STL algorithms, discussed in the following chapter). The resulting iterator is assigned to first, thus moving first forward to the beginning of the token. Then, as long as the end of the input is not reached and the predicate is satisfied, input characters are copied into word. Finally, the TokenIterator object is returned and must be dereferenced to access the new token.

The postfix increment requires a proxy object to hold the value before the increment, so it can be returned. Producing the actual value is a straightforward operator*. The only other functions that must be defined for an output iterator are the operator== and operator!= to indicate whether the TokenIterator has reached the end of its input. You can see that the argument for operator== is ignored—it only cares about whether it has reached its internal last iterator. Notice that operator!= is defined in terms of operator==.

A good test of TokenIterator includes a number of different sources of input characters, including a streambuf_iterator, a char*, and a deque<char>::iterator. Finally, the original word list problem is solved:

//: C07:TokenIteratorTest.cpp

//{-msc}

#include "TokenIterator.h"

#include "../require.h"

#include <fstream>

#include <iostream>

#include <vector>

#include <deque>

#include <set>

using namespace std;

int main(int argc, char* argv[]) {

  char* fname = "TokenIteratorTest.cpp";

  if(argc > 1) fname = argv[1];

  ifstream in(fname);

  assure(in, fname);

  ostream_iterator<string> out(cout, "\n");

  typedef istreambuf_iterator<char> IsbIt;

  IsbIt begin(in), isbEnd;

  Delimiters

    delimiters(" \t\n~;()\"<>:{}[]+-=&*#.,/\\");

  TokenIterator<IsbIt, Delimiters>

    wordIter(begin, isbEnd, delimiters),

    end;

  vector<string> wordlist;

  copy(wordIter, end, back_inserter(wordlist));

  // Output results:

  copy(wordlist.begin(), wordlist.end(), out);

  *out++ = "-----------------------------------";

  // Use a char array as the source:

  char* cp =

    "typedef std::istreambuf_iterator<char> It";

  TokenIterator<char*, Delimiters>

    charIter(cp, cp + strlen(cp), delimiters),

    end2;

  vector<string> wordlist2;

  copy(charIter, end2, back_inserter(wordlist2));

  copy(wordlist2.begin(), wordlist2.end(), out);

  *out++ = "-----------------------------------";

  // Use a deque<char> as the source:

  ifstream in2("TokenIteratorTest.cpp");

  deque<char> dc;

  copy(IsbIt(in2), IsbIt(), back_inserter(dc));

  TokenIterator<deque<char>::iterator,Delimiters>

    dcIter(dc.begin(), dc.end(), delimiters),

    end3;

  vector<string> wordlist3;

  copy(dcIter, end3, back_inserter(wordlist3));

  copy(wordlist3.begin(), wordlist3.end(), out);

  *out++ = "-----------------------------------";

  // Reproduce the Wordlist.cpp example:

  ifstream in3("TokenIteratorTest.cpp");

  TokenIterator<IsbIt, Delimiters>

    wordIter2((IsbIt(in3)), isbEnd, delimiters);

  set<string> wordlist4;

  while(wordIter2 != end)

    wordlist4.insert(*wordIter2++);

  copy(wordlist4.begin(), wordlist4.end(), out);

} ///:~

When using an istreambuf_iterator, you create one to attach to the istream object and one with the default constructor as the past-the-end marker. Both are used to create the TokenIterator that will actually produce the tokens; the default constructor produces the faux TokenIterator past-the-end sentinel. (This is just a placeholder and, as mentioned previously, is actually ignored.) The TokenIterator produces strings that are inserted into a container which must, naturally, be a container of string—here a vector<string> is used in all cases except the last. (You could also concatenate the results onto a string.) Other than that, a TokenIterator works like any other input iterator.

The strangest thing in the previous program is the declaration of wordIter2. Note the extra parentheses in the first argument to the constructor. Without these, a conforming compiler will think that wordIter2 is a prototype for a function that has three arguments and returns a TokenIterator<IsbIt, Delimiters>.[98] (Microsoft’s Visual C++ .NET compiler accepts it without the extra parentheses, but it shouldn’t.).

stack

The stack, along with the queue and priority_queue, are classified as adapters, which means they adapt one of the basic sequence containers to store their data. This is an unfortunate case of confusing what something does with the details of its underlying implementation—the fact that these are called "adapters" is of primary value only to the creator of the library. When you use them, you generally don’t care that they’re adapters, but instead that they solve your problem. Admittedly at times it’s useful to know that you can choose an alternate implementation or build an adapter from an existing container object, but that’s generally one level removed from the adapter’s behavior. So, while you may see it emphasized elsewhere that a particular container is an adapter, we’ll only point out that fact when it’s useful. Note that each type of adapter has a default container that it’s built upon, and this default is the most sensible implementation. In most cases you won’t need to concern yourself with the underlying implementation.

вернуться

98

For a detailed explanation of this oddity, see Items 6 and 29 in Scott Meyer’s Effective STL.