tags:

views:

108

answers:

1

In order to traverse grids with data in a fast and flexible way I set up an abstract, templated GridDataStructure class. The data should be accessed by STL iterators. When someone uses the class, he should not worry about which kind of STL iterator is appropriate for a specific subclass.

A solution to this problem seems to be http://stackoverflow.com/questions/2191724/using-iterators-to-hide-internal-container-and-achieve-generic-operation-over-a-b. However, I do not get why the begin() and end() members are not virtual anymore. Next to that I could not figure out where exactly the necessary methods for the STL iterator class (like operator++, operator* etc.) should be implemented.

Could you please have a look whether or not I make a design mistake? Important to me is a flexible design, but not at the cost of performance.

My class design:

template<class T>
class GridDataStructure
{
public:
    virtual iterator begin() = 0;
    virtual iterator end() = 0;
};

template<class T>
class GridDataUniform : GridDataStructure
{
public:
    GridDataUniform(int size);        

    iterator begin();
    iterator end();

    class iterator : public std::iterator<std::forward_iterator_tag, T> {
    public:
      iterator(Node* p) : node_(p) {}
      ~iterator() {}

      iterator& operator=(const iterator& other);
      bool operator==(const iterator& other);
      bool operator!=(const iterator& other);
      iterator& operator++();
      iterator& operator++(int);
      T& operator*();
      T* operator->();

    private:
      Node* node_;
    };

    private:
        T* griddata;
};

I would like to access my grid container in STL style, like:

GridDataStructure<int>::iterator = someGrid->begin(); // where someGrid is an instance of GridDataUniform
std::cout << *(iter) << std::endl;

Any help is highly appreciated.

Edit (19.10.10): Added nested iterator class

Edit (20.10.10): Added code:

template<class T>
class GridDataStructureBase
{
protected:
class BaseIteratorImpl
{
    virtual iterator begin() = 0;
    virtual iterator end() = 0;
    virtual iterator& operator++() = 0;
}

public:
class iterator : std::iterator<std::forward_iterator_tag, T>
{
public:
    iterator(const BaseIteratorImpl& itImpl) {}
    iterator begin() { return itImpl->begin(); }
    iterator end() { return itImpl->end(); }
    iterator& operator++() { return itImpl->operator++() }

private:
    BaseIteratorImpl* itImpl;

};

iterator begin()
{
    iterator* i = new iterator(??);
    return i->begin();
}

iterator end()
{
    return iterator(NULL);
}

};
+1  A: 

In the solution, begin and end don't need to be virtual, because they just call BaseIteratorImpl::begin and BaseIteratorImpl::end which are virtual.

In your specific case, you could just make begin and end virtual and not do any forwarding and it would be able to do what you want. The solution you pointed to is if you want different style iterators over the same structure, not just structure-iterator pairings which it seems you want.

EDIT: Here's something to start with (not tested or even compiled) -- might not compile and will leak (write destructors, copy ctors, op=, where you need to) -- just to get you started on the idea.

template <class T>
class GridIteratorImplBase {
   public:
      virtual GridIteratorImplBase<T>& operator++() = 0;
      virtual T& operator*() = 0;
};

template <class T>
class GridIterator {
   private:
      GridIteratorImplBase<T> *baseImpl;
   public:
      GridIterator(GridIteratorImplBase<T> *b) :baseImpl(b) {}
      GridIterator& operator++() { baseImpl->operator++(); return *this;}
      T& operator*() { return baseImpl->operator*(); }


  // you need to write a dtor, copy ctor and op= or this will leak
  // copy ctor and op= need to make new objects that are copies of baseImpl, dtor needs to delete -- make sure not to share baseImpl between two GridIterator objects
};


template <class T>
class Grid {
   virtual GridIterator<T> begin() = 0;
   virtual GridIterator<T> end() = 0;
};


template <class T>
class GridUniform {

  template <class T>
  class GridUniformIterator : GridIteratorImplBase<T>
      private T* current;
   public:
      GridUniformIterator(T* c) : current(c) {}
      virtual GridIteratorImplBase<T>& operator++() { current++; return *this; }
      virtual T& operator*() { return *current; }
  };

  GridIterator<T> begin() { 
      GridIterator<T> iter(new GridUniformIterator(gridData)); 
      return iter; 
  }
  GridIterator<T> end() { 
      GridIterator<T> iter(new GridUniformIterator(gridData+size));
      return iter; 
  }


  private:
    T* gridData;
    int size;
};

I typed this directly in to the text area of this answer -- not a compiler. It's meant to give you the idea so you can get started.

  1. begin and end are supposed to create iterators
  2. iterators need to be able to be copy constructed and have operator= called on them. If you try to have one base class for them, they will get casted to the base, so you can't use virtual for them
  3. To get around #2, you make iterators just hold onto a pointer to a base class of an iterator implementation.
Lou Franco
Thanks for your clear answer. I indeed misunderstood the main goal of the BaseIteratorImpl. I indeed want structure-iterator pairings. I implemented the iterator for GridDataUniform (and my other subclasses) as a nested class (subclass of std::iterator<std::forward_iterator_tag, T>) and implemented the methods like operator++() and operator*().However, I am unsure about how to structure the code in such a way that I can make the general calls like this: GridDataStructure<int>::iterator = someGrid->begin(); // someGrid is instance of GridDataUniform std::cout << *(iter) << std::endl;
cpk
You should probably add your current code to your question and point out the part you are having a problem with.
Lou Franco
I added the nested iterator class with its methods. Hopefully that clears things up?
cpk
Now that I see where you are going, you are going to need to do a iterator that contains an iterImpl like the question you pointed to did. The issue is that your base container can't know the return type -- so it's going to have to forward to the actual type.
Lou Franco
This is where I am lost. Should I make a nested superclass in GridDataBase, inherit it from std::iterator<std::forward_iterator_tag, T> and then inherit iterator from it?
cpk
The iterator you return will not be derived from. It will have a pointer to a BaseIteratorImpl (abstract). Then derive from BaseIteratorImpl for a real iterator. iterator::begin constructs an iterator and assigns its internal pointer the concrete subclass of BaseIteratorImpl. Then ++ just forwards from iterator through the pointer to the subclass of BaseIteratorImpl.
Lou Franco
Coded my understanding of your answer as an edit of my original post. Do I get it right? In GridDataStructureBase::begin() I am wondering how to pass the right pointer of the concrete subclass of BaseIteratorImpl. It should be created by the concrete subclass of my base container, right? I do not see how to fit that in the class structure. Are the methods and return types of my abstract BaseIteratorImpl class right?
cpk
Another related question. When all iterator operator methods become virtual, wouldn't it hurt performance a lot?
cpk
It depends on how often you call them, what else you do, and how fast you need it to be. If you need maximum performance, then yes, but virtual calls are just an extra array index (so a multiply and addition) for each virtual call.
Lou Franco
Thanks, I'll go for virtual. Would you have some spare minutes to react on my other question (2010-10-20 15:33:54Z) as well? That would be great!
cpk
Thanks for your invested time. I implemented the idea. It compiles and works. I inherited GridIterator from STL iterator to make it compatible with the STL algos. Once again: thanks a lot!
cpk