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
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
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
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.
4
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
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