Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Give access to encapsulated container

Tags:

c++

iterator

stl

class X {
  public:
    typedef std::list<int> Container;

    // (1)
    const Container& GetElements() const;

    // (2)
    Container::iterator ElementBegin();
    Container::iterator ElementEnd();

    // (3)
    CustomIterator GetElementIterator();

  private:
    Container m_container;
};

I'm looking for a consistent and clean way of providing iterators to encapsulated containers to the caller. I came up with the three ideas marked in the source code above.

  1. Provides size(), begin() and end(), all perfect for read access. However, because the returned Container reference is const, you'll only be able to use const_iterator. Returning the reference non-const is bad, because the container itself could be modified (e.g. clear()).
  2. Provides non-const access to the elements, however we'd often need an own size() method (like GetElementCount()). iterator::distance() could be used, but that may be inefficient for some containers (where operator++/-- is called repeatedly to calculate the distance).
  3. Provides a custom iterator with methods like next() etc. Still an own size() method is required.

I highly bet there're nicer solutions, so if you know any, I'd be glad to see them.

like image 708
stschindler Avatar asked Jul 26 '11 07:07

stschindler


4 Answers

A mix of (2) and (3) would probably be what I'd do :

class X {
  public :
    typedef std::list<int> ElementContainer;
    typedef ElementContainer::size_type ElementSizeType;
    typedef ElementContainer::iterator ElementIterator;
    typedef ElementContainer::const_iterator ConstElementIterator;

    ElementIterator elementBegin() { return m_container.begin(); }
    ElementIterator elementEnd() { return m_container.end(); }

    ConstElementIterator elementBegin() const { return m_container.begin(); }
    ConstElementIterator elementEnd() const { return m_container.end(); }

    ElementSizeType elementSize() const { return m_container.size(); }

  private :
    ElementContainer m_container;
};

It still leaves room to write custom iterators (by changing the typedefs), but as long as the ones provided by the container are ok, they can be used.

like image 58
Sander De Dycker Avatar answered Nov 13 '22 13:11

Sander De Dycker


I would use these names instead : iterator, const_iterator, begin, end, cbegin, cend and size() as:

class X 
{
  public :

    typedef std::list<int>::iterator iterator;
    typedef std::list<int>::const_iterator const_iterator ;

    iterator begin() { return m_container.begin(); }
    iterator end() { return m_container.end(); }

    const_iterator cbegin() const { return m_container.begin(); }
    const_iterator cend() const { return m_container.end(); }

    size_t size() const { return m_container.size(); }

  private :
    std::list<int> m_container;
};

And if you can use C++0x, then use m_container.cbegin() and m_container.cend() as:

const_iterator cbegin() const { return m_container.cbegin(); }
const_iterator cend() const { return m_container.cend(); }
like image 25
Nawaz Avatar answered Nov 13 '22 13:11

Nawaz


I can't think of much cleaner methods; you might consider the lightweight (4) solution giving access with

const Container& container() const { return m_container; }

I would prefer (3) since the container type becomes fully encapsulated, i.e. your type does not necessarily require inclusion of and you can change the container type without recompiling depending modules.

like image 1
thiton Avatar answered Nov 13 '22 13:11

thiton


Off course, the simplest is this :

class X {
  public:
    typedef std::list<int> Container;

    Container m_container;
};

but that makes your class X obsolete.

Other then that, if you really like your class, then add next methods :

Container::const_iterator ElementBegin() const;
Container::const_iterator ElementEnd() const;
int size() const;
like image 1
BЈовић Avatar answered Nov 13 '22 15:11

BЈовић