views:

226

answers:

5

I'd like to design a class Foo that stores various data of different types and returns iterators over them. It's supposed to be generic, so the user of Foo does not know how the data is stored (Foo could be using std::set or std::vector or whatever).

I'm tempted to write an interface like this:

class Foo {
  class FooImpl;
  FooImpl* impl_;
public:
  const Iterator<std::string>& GetStrings() const;
  const Iterator<int>& GetInts() const;
};

where Iterator is something like this (like iterators in .NET):

template<class T>
class Iterator {
public:
  const T& Value() const = 0;
  bool Done() const = 0;
  void Next() = 0;
};

But I know that kind of iterator is not standard in C++, and it's better to use iterators the way the STL does, so you can use the STL algorithms on them.

How can I do that? (Do I need iterator_traits by any chance?)

+1  A: 

Use a typedef to return an boost::iterator_range. For example (never mind the names),

class Container
{
     typedef std::vector<int> Collection; 

     public:
     typedef boost::iterator_range<Collection::iterator> CollectionRange;
     typedef Collection::iterator CollectionIterator;
     Range range() const {
          return make_iterator_range(collection_.begin(), collection_.end());
     }

     private:
     Collection collection_;          
};

The user code will be

Container c;
// ...
FOREACH(int i, c.range()) { //... }
Container::Range r = c.range();
for(Container::iterator j = r.begin(); j!= r.end(); j++) { // ... }

This is not generic, but the same idea can be used with templates.

Amit Kumar
Nice, but that's still exposing the `std::vector<int>` in the header file. I consider the use of `vector` an implementation detail, and ideally I'd like to hide that in the implementation file.
I guess then you will have to use pimpl over Container.
Amit Kumar
@dehmann: Is hiding that you include <vector> a practical concern or just a theoretical one? Document that they should use the typedef in your class, and leave it at that. Anyone willing to ignore your instructions and go off reading the code to find implementation details is going to cause you bigger problems than this, anyway.
Roger Pate
@roger: Yeah, it's more of a theoretical concern. I'd like to be able to "plug in any implementation", without changing the interface. But maybe I'll just give up on that ideal.
@dehmann: Remember that you *should* be able to change the header to "just plug in" another implementation and have everything Just Work(tm). (For example, changing a typedef from "vector" to "deque" or "something_else".) This is because the STL-style uses nested iterator typedefs and so forth. You will have to recompile because this does change the *binary interface* of your class, but not the *source code interface*.
Roger Pate
@dehmann my c++ isn't the best, but I believe that you can't hide that sort of implementation detail without writing abstract classes and extending them, the typedef normally is enough to hide different implementations by allowing an uniform way to use them
josefx
+1  A: 

A c++ class with iterators has to provide at least two functions if they have to work with the std library

iterator begin() //returns an iterator at starting pos
iterator end() //returns an iterator one past end or just invald

The iterator has to overload the increment operators, equals and *

iterator operator++()
iterator operator==()//make sure that an invalid iterator equals end()
T& operator*()

You can use the iterator class to wrap around the iterator of the internal storage to ensure that the user is limited to these methods.

template <typename T> iter
{
   iter(T::iterator& intern)
   T::value_type& operator*(){return *intern}
  iter operator++(){return iter(++intern);}
  bool operator==(iter const& other)const{return intern == other.intern;}
}

Where T is the type of your container.(The class is incomplete and I may have mixed something up)

josefx
A good attempt at succinctly explaining iterator basics, but in this case a bit more info is required, such as http://www.sgi.com/tech/stl/Iterators.html.
Roger Pate
@Roger Pate I thought the basics would be enougth for the generic access and it is as far as I can see the same as the .Net version posted. I might have misunderstood the question (english is my 2nd language)
josefx
@josefx: You've described forward iterators, which is all some languages have. There's quite a bit more subtlety in C++ iterators. Methods named begin/end aren't *required* per se, as the stdlib never calls them, but you do need to support STL-style iterator ranges to work with things like <algorithm>. I wouldn't call your answer wrong, but it is too much over-simplification for my taste, and I thought linking to more compleat reference appropriate.
Roger Pate
+3  A: 

Do you understand why the STL chose to put iterator implementation details in the header file? JIT frameworks are able to inline across compilation units, but C++ can only inline within a compilation unit. Advancing through a sequence is much faster when inlined, the cost of a function call dominates actually traversing the data structure.

If you really want to hide the implementation details, go ahead. You could make an STL-compatible iterator that implements operator++ and operator!= and operator-> in terms of protected virtual functions, the Next, Done, and Value you've mentioned would be decent names. Just expect to pay for the encapsulation with lower performance.

Ben Voigt
That kind of iterator is what the OP is familiar with, but I think he's asking how to write one that follows the conventions from the C++ stdlib, rather than how to break those conventions.
Roger Pate
What part of writing operator++, operator!=, and operator-> that delegate to protected virtual functions is unclear? The public user interface is then the operators, just like containers from the C++ Standard Library.
Ben Voigt
I mean the conventions you pointed out in your first paragraph, that impact performance. "But I know ... it's better to use iterators the way the STL does... How can I do that?"
Roger Pate
A: 

To fulfill the requirement that the particular container (vector, set, ...) is not mentioned in the header file, and the user will be able to iterate over all strings, is to use the visitor pattern. The downside, of course, is that the user won't be able to use the STL algorithms on the strings.

// foo.h
class StringVisitor {
public:
  void accept(const std::string& str) {
    std::cout << str << std::endl;
  }
};
class Foo {
  class Impl;
  Impl* impl_;
public:
  Foo();
  ~Foo();
  void VisitStrings(StringVisitor v) const;
};

// foo.cc
class Foo::Impl {
  typedef std::vector<std::string> StringContainer;
  StringContainer str_;
public:
  Impl() {
    str_.push_back("a");
    str_.push_back("b");
  }
  void VisitStrings(StringVisitor v) const {
    for(StringContainer::const_iterator it = str_.begin();
    it != str_.end(); ++it){
      v.accept(*it);
    }
  }  
};

Foo::Foo() : impl_(new Impl()) {}
Foo::~Foo() {delete impl_;}
void Foo::VisitStrings(StringVisitor v) const {
  impl_->VisitStrings(v);
}

// main.cc
int main() {
  Foo foo;
  foo.VisitStrings(StringVisitor());
  return 0;
}
Why are you answering your own question (and so soon, too) in a way that seems to be going off in a completely different direction? It almost appears like you're trying to espouse a certain argumentative viewpoint with this question and then answer.
Roger Pate
Well, I kept thinking about it and found an alternative solution for these certain requirements. It doesn't work with the STL algorithms, but it's still good to document it here as an alternative way to access the class-internal containers.
What certain requirements? (Putting them into the question could help---all I see is some narrative and then asking how to write STL-style iterators.)
Roger Pate
@roger: The requirement was to be able to iterate over the data, but in such a way that the Foo interface doesn't expose the way in which it stores that data. I thought the question made that clear, and my answer made clear that it addresses those requirements?
@dehmann: This relates to my other comment regarding theoretical vs practical. In `struct S { typedef int* iterator; };` it "exposes" that it uses pointers (to anyone reading the header file), but requires you to use the S::iterator typedef, and in that way, doesn't require users of the class to know that it uses pointers. Its use of pointers is encapsulated, because I can change that typedef and anything correctly written will continue to work; and in that way the pointers are *not* exposed.
Roger Pate
If you're worried about people that either 1) disregard your documentation completely, or 2) will read your header file to "expose" these details, then you have bigger problems to consider, IMHO. (And it wasn't my downvote, BTW.) --- Looks like I forgot to say that that requirement wasn't clear to me, and I tried to show how "do not expose" could be interpreted two different ways (and which way I think is more important).
Roger Pate
+1  A: 

It almost looks like you're trying to create container-independent code, which is not (in general) a good idea, unless you are writing an algorithm which can operate solely with iterators. (See Scott Myers Effective STL Item 2: Beware the illusion of container-independent code)

The problem is that most of the standard containers do not provide overlapping functionality. If you're writing code for a particular container, assume you're writing code for that container. Don't bother trying to make it container-independent.

Billy ONeal
Thanks for the link to Scott Myer's item.