Re: Constness with standard containters of pointers
On 17 mai, 11:44, Stuart Golodetz
<sgolod...@NdOiSaPlA.pMiPpLeExA.ScEom> wrote:
James Kanze wrote:
On 17 mai, 02:46, Javier <jjeron...@gmail.com> wrote:
The OP had the function returning a _reference_.
That's the point. I want to return a reference to the member
attribute, but avoiding the user to make any changes to the
container and its content.
The question then is why? Why not a copy?
I'm assuming you've probably already anticipated this reply
and have an answer ready, but I'll oblige by putting my foot
in it anyway... :-)
"Because if n is the size of the list, making a copy is a
Theta(n) operation, whereas simply returning a reference (were
it possible in this case) would have been constant time."
In other words, you've already tried this solution, and found it
too slow.
The question then might be why you are using std::list, and not
std::vector or std::deque? If you can use one of them,
particularly std::vector, I think you'll find that your
performance problem disappears. It's true that there will be n
copies, but copying a pointer is ridiculously cheap; what's
causing the slowdown is probably the fact that std::list does an
allocation as well for each copy.
Another alternative---probably the best in the long run, at any
rate, is to define what the user wants to do with this list, and
provide an interface directly in your object for doing it. If
nothing else, you can easily provide iterators into it, by
wrapping the std::list<>::iterator. (You might have a look at
boost::iterator for this. If nothing else, it will save some
typing.)
It could be argued though that you should consider the real
reason for returning the list when doing this sort of thing:
if you just want clients of the class to be able to iterate
over the list, then you might be better off returning an
iterator (*) than the list itself (thus avoiding exposing an
aspect of the class's implementation). This allows you to get
round the type problem with the list, since for a list element
type T, you can convert from a T* to a const T* without any
problems.
Exactly. The real question is more along the lines of: what
does the user do with the list (or the reference to the list)
that it gets? And wouldn't it be sensible to make this possible
directly from the interface of your class, so that you don't
(directly or indirectly) expose the internals of your object,
i.e. the fact that it uses an std::list<>.
(*) I don't necessarily mean "iterator" in the sense of a
C++-style iterator here - it's just a generic term for
something which will let you iterate over all the elements of
the list.
For better or for worse, we're stuck with the C++ style
iterators. It's what C++ programmers know best. So while I
greatly prefer a GoF iterator, and will always provide an
interface for it when reasonably possible, any iterator which
might be used in generic code, or might be used by client code,
will support the STL idiom, and any code that I write which
might end up being generic (and thus need to iterate over an STL
container) will use the STL iterators, even when my iterators
support a simpler and more intuitive idiom.
The problems with the STL iterators are well known, and
Boost::iterator goes a long way in compensating for them. In
the case where you are simply wrapping another iterator, to
change the return type of operator*(), of course, it's fairly
easy... to screw up:-). An input iterator is trivial, but the
operator* of a forward iterator must return a reference, so you
have to cache the converted value somewhere. (Technically, I
think that even that is wrong, since the reference is required
to be in the container. But if the iterator doesn't allow
mutation, it's a silly requirement. Still, it does mean that
&*c.begin() != &*c.begin().)
--
James Kanze (GABI Software) email:james.kanze@gmail.com
Conseils en informatique orient=E9e objet/
Beratung in objektorientierter Datenverarbeitung
9 place S=E9mard, 78210 St.-Cyr-l'=C9cole, France, +33 (0)1 30 23 00 34