32

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?

  • 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 :-) – Philip Kendall 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
25

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.

  • 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
  • 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. – Patrick Niedzielski 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
  • How is this hiding the implementation details? – BЈовић 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
4

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;
};
  • 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
  • @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. – underscore_d 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.

  • 1
    This is CORRECT...complete implementation hiding and no additional overhead. – Abstraction is everything. May 18 '17 at 3:25
  • 2
    @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. – underscore_d 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... – Abstraction is everything. 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.

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

  • 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?) – underscore_d Oct 16 '18 at 21:16

protected by Community Dec 9 '14 at 3:54

Thank you for your interest in this question. Because it has attracted low-quality or spam answers that had to be removed, posting an answer now requires 10 reputation on this site (the association bonus does not count).

Would you like to answer one of these unanswered questions instead?

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