36

I have a class that represents a list of people.

class AddressBook
{
public:
  AddressBook();

private:
  std::vector<People> people;
}

I want to allow clients to iterate over the vector of people. The first thought I had was simply:

std::vector<People> & getPeople { return people; }

However, I do not want to leak the implementation details to the client. I may want to maintain certain invariants when the vector is modified, and I lose control over these invariants when I leak the implementation.

What's the best way to allow iteration without leaking the internals?

8
  • 2
    First of all, if you want to maintain control, you should return your vector as a const reference. You'd still expose implementation details that way, so I recommend making your class iterable and never exposing your data structure (maybe it will be a hash table tomorrow?).
    – idoby
    Dec 6 '14 at 7:52
  • A quick google search revealed me this example: sourcemaking.com/design_patterns/Iterator/cpp/1
    – Doc Brown
    Dec 6 '14 at 7:57
  • 1
    What @DocBrown says is likely the appropriate solution - in practice this means you give your AddressBook class a begin() and end() method (plus const overloads and eventually also cbegin/cend) which simply return the vector's begin() and end(). By doing so your class will also be usable by all most std algorythms.
    – stijn
    Dec 6 '14 at 8:42
  • 1
    @stijn That should be an answer, not a comment :-) Dec 6 '14 at 8:47
  • 1
    @stijn No, that is not what DocBrown and the linked article says. The correct solution is to use a proxy class pointing to the container class along with a safe mechanism for indicating position. Returning the vector's begin() and end() are dangerous because (1) those types are vector iterators (classes) which prevents one from switching to another container such as a set. (2) If the vector is modified (e.g. grown or some items erased), some or all of the vector iterators could have been invalidated.
    – rwong
    Dec 6 '14 at 10:18
26

allow iteration without leaking the internals is exactly what the iterator pattern promises. Of course that is mainly theory so here is a practical example:

class AddressBook
{
  using peoples_t = std::vector<People>;
public:
  using iterator = peoples_t::iterator;
  using const_iterator = peoples_t::const_iterator;

  AddressBook();

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

private:
  peoples_t people;
};

You provide standard begin and end methods, just like sequences in the STL and implement them simply by forwarding to vector's method. This does leak some implementation detail namely that you're returning a vector iterator but no sane client should ever depend on that so it is imo not a concern. I've shown all overloads here but of course you can start by just providing the const version if clients should not be able to change any People entries. Using the standard naming has benefits: anyone reading the code immediately knows it provides 'standard' iteration and as such works with all common algorithms, range based for loops etc.

5
  • note: though this certainly works and is accepted it's worth taking note of rwong's comments to the question: adding an extra wrapper/proxy around vector's iterators here would make clients independent of the actual underlying iterator
    – stijn
    Dec 6 '14 at 19:32
  • 1
    In addition, note that providing a begin() and end() that just forward to the vector's begin() and end() allows the user to modify the elements in the vector itself, maybe using std::sort(). Depending on what invariants you are trying to preserve, this may or may not be acceptable. Providing begin() and end(), though, is necessary to support the C++11 range-based for loops. Dec 7 '14 at 20:29
  • You should probably also show the same code using auto as return types of iterator functions when using C++14.
    – Klaim
    Dec 9 '14 at 0:56
  • 1
    How is this hiding the implementation details? Dec 9 '14 at 9:19
  • @BЈовић by not exposing the complete vector - hiding does not necessarily mean the implementation has to be literally hidden from a header and put in the source file: if it's private client cannot access it anyway
    – stijn
    Dec 9 '14 at 9:33
8

If iteration is all you need, then perhaps a wrapper around std::for_each would suffice:

class AddressBook
{
public:
  AddressBook();

  template <class F>
  void for_each(F f) const
  {
    std::for_each(begin(people), end(people), f);
  }

private:
  std::vector<People> people;
};
2
  • 1
    It would probably better to enforce a const iteration with cbegin/cend. But that solution is by far better than giving access to the underlying container.
    – galop1n
    Dec 9 '14 at 19:09
  • 1
    @galop1n It does enforce a const iteration. The for_each() is a const member function. Hence, the member people is seen as const. Hence, begin() and end() will overload as const. Hence, they will return const_iterators to people. Hence, f() will receive a People const&. Writing cbegin()/cend() here will change nothing, in practice, though as an obsessive user of const I might argue it's still worth doing, as (a) why not; it's just 2 chars, (b) I like saying what I mean, at least with const, (c) it guards against accidentally pasting somewhere non-const, etc. Oct 16 '18 at 21:03
3

You can use the pimpl idiom, and provide methods to iterate over the container.

In the header :

typedef People* PeopleIt;

class AddressBook
{
public:
  AddressBook();


  PeopleIt begin();
  PeopleIt begin() const;
  PeopleIt end();
  PeopleIt end() const;

private:
  struct Imp;
  std::unique_ptr<Imp> pimpl;
};

In the source :

struct AddressBook::Imp
{
  std::vector<People> people;
};

PeopleIt AddressBook::begin()
{
  return &pimpl->people[0];
}

This way, if your client uses the typedef from the header, they will not notice what kind of container you are using. And the implementation details are completely hidden.

3
  • 1
    This is CORRECT...complete implementation hiding and no additional overhead. May 18 '17 at 3:25
  • 3
    @Abstractioniseverything. "no additional overhead" is plainly false. PImpl adds a dynamic memory allocation (and, later, free) for every instance, and a pointer indirection (at least 1) for every method that goes through it. Whether that is much overhead for any given situation depends on benchmarking/profiling, and in many cases it's probably perfectly fine, but it is absolutely not true - and I think rather irresponsible - to proclaim that it has no overhead. Oct 16 '18 at 21:11
  • @underscore_d I agree; not meaning to be irresponsible there, but, I guess that I fell prey to the context. “No additional overhead...” is technically incorrect, as you deftly pointed out; apologies... Oct 17 '18 at 2:25
1

One could provide member functions:

size_t Count() const
People& Get(size_t i)

Which allow access without exposing implementation details (like contiguity) and use these within an iterator class:

class Iterator
{
    AddressBook* addressBook_;
    size_t index_;

public:
    Iterator(AddressBook& addressBook, size_t index=0) 
    : addressBook_(&addressBook), index_(index) {}

    People& operator*()
    {
        return addressBook_->Get(index_);
    }

    Iterator& operator ++ ()
    {
       ++index_;
       return *this;
    }

    bool operator != (const Iterator& i) const
    {
        assert(addressBook_ == i.addressBook_);
        return index_ != i.index_;
    }
};

Iterators can then be returned by the address book as follows:

AddressBook::Iterator AddressBook::begin()
{
    return Iterator(this);
}

AddressBook::Iterator AddressBook::end()
{
    return Iterator(this, Count());
}

You'd probably need to flesh the iterator class out with traits etc but I think that this will do what you've asked.

0
1

if you want exact implementation of functions from std::vector, use private inheritance as below and control what is exposed.

template <typename T>
class myvec : private std::vector<T>
{
public:
    using std::vector<T>::begin;
    using std::vector<T>::end;
    using std::vector<T>::push_back;
};

Edit: This is not recomended if you also want to hide internal data structure i.e. std::vector

1
  • Inheritance in such a situation is at best very lazy (you should use composition and provide forwarding methods, especially since there are so few to forward here), often confusing and inconvenient (what if you want to add your own methods that conflict with vector ones, which you never want to use but nonetheless must inherit?), and maybe actively dangerous (what if the class being lazily inherited from might be deleted through a pointer to that base type somewhere, but it [irresponsibly] didn't protect against destruction of a derived obj via such a pointer, so simply destroying it is UB?) Oct 16 '18 at 21:16

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