Traversing another class' private vector?

This topic is 2879 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

Recommended Posts

I've come into a bit of a problem with a private vector. I have 2 classes: A has a private vector of pointers. B needs to access each one of those pointers. I don't want to make the vector public since it probably should be private since it shouldn't be able to be cleared or have anything added to it from outside. I need B to somehow be able to traverse the vector and access the pointers or at least access them all somehow. How can I do that? I had some idea of passing in an iterator in an accessing function but since B doesn't know the size of the private vector, I quickly scrapped that. Would it be best just to pass a vector within B to a function in A and have that function copy all elements from the private vector to the temporary vector of B? [Edited by - Sean_Seanston on March 7, 2010 11:31:17 AM]

Share on other sites
putting
friend class B;

anywhere in the declaration of class A will make every private/protected member accessible (only) for class B.
is that what you need?

ps: there are some things with the friend "relationship" you should know of and I can't find a good tutorial/paper anywhere so I'll try to recall them myself:
1) It's a one-way "relationship" if B is a friend of A, A isn't a friend of B.
2) classes that inherit from B are not friend with A (not 100% sure though).
3) you can also only declare functions as friend:
friend void doFoo(int);friend int B::justAFunction();

Share on other sites
Give A a begin() and end() function that return a const_iterator into the vector

Share on other sites
Quote:
 Original post by Sean_SeanstonB needs to access each one of those pointers.

Why?

Share on other sites
struct Container {  template < class Visitor >  void visit(Visitor v) {    std::for_each(foos.begin(), foos.end(), v);  }private:  std::vector<Foo*> foos;];

Share on other sites
Quote:
Original post by Zahlman
Quote:
 Original post by Sean_SeanstonB needs to access each one of those pointers.

Why?

It needs to get an identifier from the objects that are pointed to, so that it can initialize some objects.

A is a Flyweight for B.

Share on other sites
Quote:
 Original post by Antheusstruct Container { template < class Visitor > void visit(Visitor v) { std::for_each(foos.begin(), foos.end(), v); }private: std::vector foos;];

This is still somewhat less flexible than just exposing an iterator to the container. For example:

class Foo{   typedef std::vector<int> ItemList_t;private:   ItemList_t items;public:   ItemList_t::iterator begin() { return items.begin(); }   ItemList_t::iterator end() { return items.end(); }   ItemList_t::const_iterator begin() const { return items.begin(); }   ItemList_t::const_iterator end() const { return items.end(); }};

For one thing, it's less code because unless your compiler supports C++0x lambdas (or you already have written a function whose sole purpose is to perform the operation of interest on these pointers) you're going to have to create a class to operate on them and then use it as a function object. This usually ends up being a lot of code for such simple matters, and creating thousands of little function objects for highly specialized purposes ends up not being very scalable in the long run.

Secondly, it allows you to base your logic on complicated relationships between the objects, with the simple visit approach each entry in the list is treated independently of the others. For example, suppose for whatever strange reason you wanted to do the following, assuming you've defined iter and end to refer to vec.begin() and vec.end() respectively.

while (iter != end){   iterator next = iter;   ++next;   if (next != end)      do_something_weird(*iter, *next);   iter = next;}

It's *possible* with the function object approach, but it's already kind of unintuitive and rapidly starts becoming less and less intuitive as you change things / make things more complicated.

For these and probably other reasons which aren't immediately popping into my head, I've adopted a pretty much universal approach of just always exposing the iterator type via a public typedef and then return both iterators and const_iterators from the class.

Share on other sites
Can you have a method on A that returns the vector by const reference? More than just B would be able to see it, but you wouldn't have to write any boilerplate.
typedef std::vector<Foo*> FooVec;class A{    FooVec vec;    public:    const FooVec& GetVec() const{ //provides read-only access to the vector        return vec;    }};void UseVector(A& a){    const FooVec& vec = a.GetVec();    //The function can read the vector,    //but can't write it because it is const    //even if the A object isn't}

Share on other sites
Hmmm... I hadn't thought of that Ocelot, that seems like it might be less fiddly than cache_hit's way for my purposes.

Well, I implemented the way cache_hit suggested and that worked fine, thanks. Maybe I'll have a look at returning a const reference too. It might be quicker than making 2 iterators wherever I need to do this. Unless someone points out a fatal flaw or something.

Share on other sites
Keep in mind that if you expose the internal vector, either through const_iterators or a const reference to the vector, while you won't be able to alter the vector contents, you will allow external actors to call non-const member functions on those pointers.

Share on other sites
Quote:
 Original post by theOcelotCan you have a method on A that returns the vector by const reference? More than just B would be able to see it, but you wouldn't have to write any boilerplate.

The main thing I don't like about this, while it's true that it does enable the same basic things and is "relatively" safe, is that it couples the the client too tightly with the implementation of the class. It becomes more work, for example, if you wanted to change the internal type from vector to, say, list. By exposing the appropriate typedefs, you can loosen the coupling somewhat, although admittedly some work still has to be done.

Quote:
 Original post by SiCraneKeep in mind that if you expose the internal vector, either through const_iterators or a const reference to the vector, while you won't be able to alter the vector contents, you will allow external actors to call non-const member functions on those pointers.

This is true. boost::make_iterator_range addresses this, but the difficulty in understanding the code (and in writing it) starts growing

Share on other sites
Quote:
 The main thing I don't like about this, while it's true that it does enable the same basic things and is "relatively" safe, is that it couples the the client too tightly with the implementation of the class. It becomes more work, for example, if you wanted to change the internal type from vector to, say, list. By exposing the appropriate typedefs, you can loosen the coupling somewhat, although admittedly some work still has to be done.

You have a bit of the same problem handing out iterators too: if you give out random access iterators and then want to switch to list, you'd break all the code that takes advantage of random access.

If you want to keep switching to list an open option, perhaps you should wrap the vector's iterators into a facade that provides only bidirectional operations.

Share on other sites
Quote:
Original post by visitor
Quote:
 The main thing I don't like about this, while it's true that it does enable the same basic things and is "relatively" safe, is that it couples the the client too tightly with the implementation of the class. It becomes more work, for example, if you wanted to change the internal type from vector to, say, list. By exposing the appropriate typedefs, you can loosen the coupling somewhat, although admittedly some work still has to be done.

You have a bit of the same problem handing out iterators too: if you give out random access iterators and then want to switch to list, you'd break all the code that takes advantage of random access.

If you want to keep switching to list an open option, perhaps you should wrap the vector's iterators into a facade that provides only bidirectional operations.

To some extent, yea, which I mentioned. But it can be hidden in a lot of cases. For example, consider the following code:

class foo{public:   typedef std::list<int>  Container_t;   typedef Container_t::iterator iterator;   typedef Container_t::const_iterator const_iterator;private:   Container_t container_;public:   iterator begin() { return container_.begin(); }   const_iterator begin() const { return container_.begin(); }   iterator begin() { return container_.begin(); }   iterator end() { return container_.end(); }};void do_something_generically(){   foo f;   foo::const_iterator iter = f.begin();   foo::const_iterator end = f.end();   for (; iter != end; ++iter)      operate_on(*iter);}

Here, it doesn't matter if Container_t is a list, vector, deque, stack, queue, etc. Only thing that matters is you have an operate_on function that takes an int.

If you happen to be using an std::map this becomes a problem. You could work around it by providing a specialization of operate_on as such:

template<class Key, class Value>void operate_on(const std::pair<Key,Value>& pair){   operate_on(pair.second);}

For exposing the internal container directly, you could achieve something similar by additionally exposing the typedef, but to me that just feels clunky and brittle, with too many maintenance points.

I agree the difference isn't huge, at least for that one specific aspect. But I still think the iterator approach is more flexible as it allows both iterators and const iterators to be selectively exposed, and it does not allow access to arbitrary methods on the underlying container. Even by returning only a const reference to the container someone could in theory cast away the constness.

If boost::iterator_range isn't too heavyweight for your needs, you can work around even the aforementioned problem with std::pair by having an adaptor that does the adaptation internally so that the client dictates the value_type of the iterator so that it could easily provide an iterator that just returned keys, or just returned values, and that way client code wouldn't have to do any special tricks like specializing the operate function / code.