tags:

views:

175

answers:

3

I have a class which has a std::vector of child control pointer. For obvious reasons, I do not want the user of the class to have direct access to the std::vector. All I would want is a way to give the caller the pointers. What would be a good OO way to do this? (this function will be called often)

Thanks

+13  A: 

Provide a function that returns a const_iterator to the vector. It is also useful to add one to return the iterator to the end of the vector.

class MyClass {
public:
  typedef vector<T>::const_iterator c_iter;

  c_iter getBegin() const {return v.begin();}
  c_iter getEnd() const {return v.end();}

  // and perhaps if it's useful and not too invasive.
  const T& getAt(int i) const {return v.at(i);}

  //stuff
  vector<T> v;
};
JoshD
so after getting these the user could do for(it; it != itend; ++it) {}
Milo
@Milo: yes, that's right. But they would be prevented from altering the vector; they could only read it.
JoshD
Thanks! this will work great!
Milo
@Milo: You're quite welcome. I'm glad I could help.
JoshD
One thing, for getAt, what if the silly user enters an invalid index?
Milo
Often better to provide a typedef for the user, so they're far less likely to hard-code `vector<T>::const_iterator` as the type of the variable they put the retrieved iterator into, which would require edits to client code should MyClass change its data representation.
Tony
@Milo: for getAt, you should do some error checking. I'm just illustrating an idea here...
JoshD
alright thanks, I did bound checking
Milo
@Milo: The std::vector has a bounds checked access method. Loop up at(): http://www.sgi.com/tech/stl/Vector.html
Martin York
But, how would the vector `v` grow, won't the user need to insert or remove items?
ArunSaha
+3  A: 

Iterators are a good, obvious way to do this. A visitor pattern is another way to give the client code the ability to operate on each element in the vector: in some ways it's even cleaner, exposing less to the user, and allowing the container more control, e.g.:

  • there's no issue with the client having iterators that might be later invalidated
  • to obtain a mutex lock until the client code had read all entries before other threads are allowed to operate on the container
  • if you filter or synthesize the elements, you don't need to create complicated iterator proxy objects

BUT

  • the client is more strongly locked into whatever iteration you provide: e.g. you can generally step multiple independent iterators through a container, facilitating operations on multiple elements, but visitor typically runs through once before returning: any extra functionality - suspending/resuming iteration, deleting an element - needs to be specifically supported by the container's visit code (perhaps by a return code from the visitor function). (Even without explicit support, terminating iteration might be achieved by an exception). By way of contrast, with iterators a single erase function can be used on an iterator whether from begin(), incremented or not, as well as other operations like find(): this is a cleaner factoring of functionality.

That would look something like:

class Container
{
  public:
    template <typename Visitor>
    void visit(Visitor& visitor)
    {
        for (Vector::const_iterator i = v_.begin(); i != v_.end(); ++i)
             visitor(*i);
    }

  private:
    typedef std::vector<X> Vector;
    Vector v_;
};

// client code...

struct Visitor
{
    void operator()(const X&) { ... }
    // any data you want to update as you iterate...
};

Visitor v(...any construction arguments...);
container.visit(v);
Tony
+1  A: 

I usually do it something like the following:

class MyClass {
public:   
  const unsigned int GetNumberOfItems() { return v.size(); }

  T* GetItemNumber(const unsigned int n) 
  {
    // 3 options here, thrown your own exception type, or use the std one, or
    // or just return NULL meaning nothing there or out of range.
    try{
      return v.at(n);
    } catch (std::out_of_range &e){
    }

    return NULL;    
  }

  vector<T> v;
};

Then you can just do something like:

MyClass cl;
int count = cl.GetNumberOfItems();
for (int i = 0; i < cl.GetNumberOfItems(); i++){
  T* item = cl.GetItemNumber(i);
}

No iterators to the outside world required. If you have ever have to expose something like this to a standard C API then it's very easy to expose.

Matt H
Tony