Bob Hairgrove said:
For example, if index is out of range, an exception can be thrown. The
implementation can also be very complex. Consider that there might not
even be a member "array", but operator[] does a database lookup
instead (somehow). Or that the real array is held in another class,
and FooList holds a pointer or reference to that class to which it
forwards the call. There are many possibilities here.
The above is not quite true. The Foos in FooList *must* be objects in
RAM because clients of FooList may keep a pointer/reference to the value
returned, or modify the state of a FooList object by modifying the Foo
returned. That breaks the UAP (clients know that the return value was
not computed, but rather stored,) thus encapsulation is broken.
I'm sorry, but you are wrong. The reference returned may not
necessarily even be a reference (see section 23.2.5, paragraph 2 of
the C++ standard for what it says about
std::vector<bool>:
perator[]). This is probably an example of
encapsulation at its best!
By all means, show me some code. I'd love to be proven wrong...
Look at your favorite STL's implementation of vector said:
class Foo {
// implement as you see fit.
public:
int& bar(); // implement as you see fit.
};
int main() {
Foo f;
int& i = f.bar();
i = 1967;
assert( f.bar() == 1967 );
i = 1942;
assert( f.bar() == 1942 );
}
If you can implement the Foo interface such that the int returned is not
stored in RAM and the assertions in main don't abort the program, I'd
love to see how. The learning experience would be wonderful.
First, please read my follow-up post which you probably didn't see
before posting this.
Also, we were talking about operator[] which is overloaded for
const/non-const. They are two separate functions and don't necessarily
have to return the same reference at all (unless you expect them to be
consistent, which needs to have a requirement/business rule).
As to the challenge, those assertions of yours are new requirements,
and I didn't say anything about how the object whose reference
returned wasn't stored in RAM ... that's not really possible. It just
doesn't have to reference the private data member (array) contained
within the class. It can be a static object or a dummy member
variable, for example. Or you could even implement operator[] to
return a temporary proxy object which has an automatic conversion to
the reference type.
I've actually done this before for an ODBC class library wrapper I
wrote about two or three years ago. The cursor class had overloaded
operator[] which was used for reading and writing to the columns of
the current row. Since each column can have a completely different
data type, it wasn't possible to have it return a reference to the
real type at all, so we returned a proxy class which handled the
actual conversion. The gory details were all in the proxy, but
transparent to the clients. And it worked just fine, although there
was a lot of casting from raw memory buffers going on behind the
scenes. Clients could write stuff like:
OdbcConnection db(/*...*/);
// x is an int, y is a string, and z is a double...
OdbcCursor cr(db, "SELECT x,y,z FROM my_table;");
// prepare or execute query...
while(!cr.EOF()) {
cr.Edit();
cr[0] = 123;
cr[1] = "some string";
cr[2] = 3.14159;
cr.Update();
cr.Next();
}
cr.Close();
Operator[] was also overloaded to take a string argument so that
columns could be accessed by name, of course. I just used the above
for illustration purposes. (Now if I had been a little more STL savvy
at the time, I would have probably implemented iterators for
OdbcCursor...)
Even then, Foo::bar() doesn't necessarily have to return the same
value that you assign to i; it can even silently change i inside its
implementation...which is why we need to document the fact or disallow
keeping a pointer or reference to the object returned. Think about
vector<bool>:
perator[]. It must return a reference to bool, yet we
know that it is not possible to take the address of a single bit.
(Hmmm ... how do they do it??)
I will show you another example of what I am saying. Consider having a
class which controls access to elements of a vector -- conceptually
speaking, that is. Internally it doesn't have to be a vector at all
unless maybe there are O(1) time random-access requirements on it. We
want to grant access according to user permissions. You want some
users to be able to read and write all values via operator[], but
limit what other users with less permissions can read and write.
Furthermore, the stored values are encrypted, but you want to read and
write plain-text (e.g. passwords). Also, you want each user to think
that they are accessing values at index 0..n, where in reality these
are located just about anywhere in the vector or might be looked up
somewhere else. Finally, in order to prohibit doing pointer arithmetic
to access elements, just in case we are using a vector, we use proxy
elements which serialize access (i.e. one user at a time) by
implementing a locking mechanism. This prevents clients who ARE being
sneaky and holding a reference to what Foo::bar() returns from
accessing the next user's password or code -- we allocate the dummy or
proxy object dynamically for one call only, then delete it and
reallocate it on the next call, so that the previous references or
pointers would dangle (of course, we will have to document that
somewhere...<g>)
Shall I continue? I think you get where I am going... but if you want,
I'll take a few more mintues and post some code. It's not hard, but
I'm a little lazy. <g> Somehow I think we aren't really talking about
the same thing, though...