tags:

views:

157

answers:

5

I have a class that contains, among other things, an std::list. I want to expose this list but only in such a way that the structure and the data it contains are read only, but still can be used with iterators.

The way I've got it 'working' atm is to return a copy of the list. This leave my class 'safe' but of course does nothing to stop the caller from modifying their copy of the list and not getting the right data.

Is there a better way?

+11  A: 

Why not return a const std::list& instead?

JaredPar
+1: My thoughts exactly.
Chris
Because exposing a list, as such, exposes an implementation detail. When you come to your senses and decide to use a vector instead, you *don't* want code around that depends on it's being a list.
Jerry Coffin
Jared: Because apparently I've been working way too long today and have forgotten the simplest of things, thanks!!Jerry: This is a list rather than a vector is because it is part of a procedural generation function that needs to insert a new element at arbitrary locations often, but only needs to read the data sequentially.
Tim Jones
@Tim: Oftentimes, an `std::deque` is still better than `std::list` even when you need insertion in the middle. So if you can hide away that fact, its not a bad idea to do so.
Dennis Zickefoose
+3  A: 

Return a const reference:

const std::list<T>& getList() const;

or just return const iterators:

std::list<T>::const_iterator getListBegin() const;
std::list<T>::const_iterator getListEnd() const;
James McNellis
+7  A: 

Instead of exposing the list itself (at all) just expose const_iterators to its beginning and end. See cbegin() and cend() for help in doing this...

Jerry Coffin
I think `cbegin()` and `cend()` are C++0x additions; `begin()` and `end()` have const versions that return a `const_iterator` though.
James McNellis
@Mark: Not yet, but (crossing fingers) Real Soon Now!
Jerry Coffin
+2  A: 
class foo {
  private:
     typedef std::list<bar> bar_cont_t;

  public:
     typedef bar_const_t::const_iterator bar_const_iterator;

     bar_const_iterator bar_begin() const {return bar_data_.begin();}
     bar_const_iterator bar_end  () const {return bar_data_.end  ();}

     // whatever else

  private:
     bar_cont_t bar_data_;
};
sbi
+3  A: 

There is a dependency issue in exposing one's data member to the outside world.

If you decide to change your attribute for something better (because list are the last resort container), or because you have a new requirements, then all your clients will be impacted, and that is bad.

One simple alternative is to offer a typedef:

typedef std::list<Foo>::const_iterator const_iterator;

IF your clients use your alias, then it's a simple matter of recompiling the code.

Another alternative is to create your own iterator class (not that difficult) which will embed the actual iterator.

class const_iterator
{
public:

private:
  typedef std::list<Foo>::const_iterator base_type;
  base_type mBase;
};

You simply forward all the operations to the actual iterator, and your clients (though they will have to recompile if you change your container) cannot accidentally use an unaliased type.

Then, the 3rd solution is similar to the first, except that you abstract the type... it's quite inefficient though (for a list), so I would not really advise it: iterators are supposed to be cheap to copy, you don't want to new anything.

Matthieu M.
+1 for the custom iterator suggestion; see also the excellent article [On the Tension Between Object-Oriented and Generic Programming in C++, and What Type Erasure Can Do About It](http://www.artima.com/cppsource/type_erasure.html)
James McNellis
Very interesting read, thanks for the article. My preferred type-erasure example is in `shared_ptr` and its way of creating a `delete`-functor upon the time of creation. The trouble is that type erasure often means dynamic allocation (pimpl) and I always thought it annoying for types that are supposedly cheap to copy (iterators, smart-pointers).
Matthieu M.