2

I'm designing a data structure in C++, and I want to expose an interface to the user to traverse the structure in some order. Instead of creating several different types of enumerators, I want to keep it simple and provide a single enumerator, while maintaining const-correctness.

My design is summarised as follows:

class DataStructureNode; // The basic building block of the structure

class DataStructureEnumerator
{
public:

    friend class DataStructure;

    DataStructureEnumerator GetNext() { ... }
    DataStructureEnumerator GetPrevious() { ... }

private:

    const DataStructureNode* m_Node;
    ...
};

class DataStructure
{
public:

    DataStructureEnumerator GetEnumerator() { ... }

    DataStructureNode* GetNodeFromEnumerator(const DataStructureEnumerator& e)
    {
        // Let's assume this function also ensures the node belongs to this structure
        return const_cast<DataStructureEnumerator&>(e).m_Node; // <--- Ooo! const_cast!
    }

    const DataStructureNode* GetNodeFromEnumerator(const DataStructureEnumerator& e) const
    {
        // Let's assume this function also ensures the node belongs to this structure
        return e.m_Node;
    }
};

This design has the benefit that there is only one type of enumerator that handles both mutable and immutable structures.

Of course, the alternative (and the conventional) design in C++ is to have two (or more) enumerator classes, such as DataStructureEnumerator and DataStructureConstEnumerator. The conventional design comes with added code-bloat, more templates, and an uglier interface (in my opinion!), but it's the norm in C++ communities.

I'm interested in hearing opinions of the community on this design. Are there any potential pitfalls that I'm missing with my design?

6

It's probably simpler and more straightforward to just use a single template class that's compatible with iterator conventions:

template <typename T> DataIterator {
  public:
    T &operator* () const {
      return *m_node;
    }

  DataIterator &operator++ () {
    // get next node...
    return *this;
  }

  DataIterator &operator-- () {
    // get previous node...
    return *this;
  }

private:
  T *m_node;
}

This can then be used for either const or non-const versions of the type:

typedef DataIterator<Data> iterator;
typedef DataIterator<Data const> const_iterator;

This is, from what I've seen, the generally accepted way to handle "enumerators" in C++, and it's how all of the STL's containers expose their contents. Your code seems to be betraying the idea of Separation of Concerns, also, by having the DataStructure class be responsible for extracting a node from an enumerator.

1

The biggest flaw is that your enumerator is similar to the C++ concept of an iterator, but doesn't follow the interface for an iterator. This means that your enumerators can't be used in conjunction with algorithms that are based on iterators (i.e. all algorithms in the standard library and in Boost). See the answer by @giantskin for an alternative.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.