Encapsulation and Operator[]

D

Daniel T.

"Greg said:
Roger said:
Greg said:
On the contrary, FooList demonstrates exactly
how a class should encapsulate its data - with private data members
and
a public interface. Note that clients cannot access FooList's data
member directly, instead they must invoke methods in FooList's
public
interface to access FooList's data. In other words, FooList has
encapsulated its data by mediating all access to it.

I guess I don't see the difference between op[] as instantiated here
and making array public.

There is a huge difference between using operator[] and making the
array public. With a public array, clients can bypass FooList's
interface (and FooList's methods) and obtain the data FooList stores -
directly. And without FooList's interface interposing itself between
its stored data and its clients there is no easy way for FooList to use
a different data storage mechanism in the future, since its clients
will all be relying on the data being stored in an array.

Accessing the data objects through the operator[] is a completely
different story. The operator[] is a function - it is therefore code in
FooList that clients must call in order to retrieve data from a FooList
object. Since FooList can implement operator[] however it likes, it can
get the data it returns from anywhere it likes. And since every client
must call this method to get the data, no client is relying on any
detail of how FooList actually stores its data. In this example FooList
happens to use an array data member - but it need not to. For clients,
the overloaded operator[] creates the illusion that FooList is - or has
- an array. But an interface is separate from the implementation. In
fact, with operator[] access, FooList clients have no way of knowing
how FooList actually stores its data.

Please be more clear, the above is simply not the case as I have already
shown, *unless* we document that clients of FooList only use the op[] to
access the data. In other-words, what keeps the encapsulation intact is
the documentation, not the function.

I agree that is one of the primary benefits of encapsulation, but so
is data hiding. The data is not being hidden in any robust sense here.
I don't see the difference, in my example, between op[] and making
array public. It's disheartening to me to see that one of the
much-vaunted advantages of C++ over C is so easily, so naturally, so
intuitively and quite often subverted.

It's important to ask what exactly FooList aims to encapsulate - and
the answer is not Foo objects, not by any means. Simply put, FooList
does not encapsulate the Foo objects that it stores. Containment has
nothing to do with encapsulation. Since FooList does not implement Foo,
FooList cannot encapsulate Foo. Foo is an independent class with its
own interface and its own, encapsulated implementation.

So what then does FooList encapsulate? It encapsulates just what it
implements: a storage mechanism for Foo objects. That's it. But
otherwise FooList knows almost nothing about Foo objects. The classes
are not related, so FooList is just a client (and barely one at that)
of Foo.

So does the fact that FooList returns Foo objects break its
encapsulation? Absolutely not. FooList is a container - a container is
expected to return whatever it logically contains. That's why it's
called a container after all. As we can conclude from the FooList
example, a container does not encapsulate the items that it stores -
only the way that it stores them. Encapsulation is not about data
organization or relationships between classes - it describes solely the
relationship (within a single class) between a public interface and an
implementation.

Now the above I think is a good write-up, and one that I can generally
agree with. Except as follows, the FooList's sole responsibility is to
ensure that the Foos it holds have their destructors called at the
appropriate time (as the last thing that happens to the Foo object
contained.) So we must ask ourselves, can FooList make such a guarantee
with the interface provided? Well, if some member-function returns a
pointer/reference to one of the contained Foo's, it can't. It's up to
the clients of FooList to restrain from using the free access that
FooList provides.

So yes, encapsulation is broken, but that (as I have already explained)
is not necessarily a bad thing, because FooList doesn't really own the
objects it contains anyway... Ultimately, the responsibility is on the
owner of the Foos.
 
B

Bob Hairgrove

And there you go. You might want to look up the UAP (which is what I
said a reference return breaks.) The Uniform Access Principle says, "All
services offered by a module should be available through a uniform
notation, which does not betray whether they are implemented through
storage or through computation."

If you want to be consistent with this, then even having an assignment
operator "breaks encapsulation", as you say. Don't you think you are
taking this a little too far?
 
D

Daniel T.

Bob Hairgrove said:
If you want to be consistent with this, then even having an assignment
operator "breaks encapsulation", as you say. Don't you think you are
taking this a little too far?

How does having an assignment operator break the UAP? You have me
stumped...

class Range {
// invariant: range() == high() - low()
public:
int low() const;
int high() const;
int range() const;
Range& operator=( const Range& );
void low( int v );
void high( int v );
void range( int v );
};

The op= above in no way tells me what values are stored in range
objects, and what values are computed. In fact, none of low, high, or
range may be stored in RAM, all three of them may be computed.
 
R

Roger Lakner

Bob Hairgrove said:
[top-posting corrected]
It does leave room for later modifications to the implementation
without having to change the interface. Often, you will see things
like these seemingly silly "do-nothing" implementations in the
pre-production stages of code. When the preliminary testing phase
passes, programmers can "harden up" the code by adding stuff to the
implementation body. Clients only see the headers, so they wouldn't
have to recompile their own code.


Yes, that makes sense to me. But, supposedly, std::vector is no longer
in the early stages of development. And, though your exchange with
Daniel T. was a little over my head, it was very instructive. Thanks
for your response.

Roger
 
R

Roger Lakner

Greg said:
There is a huge difference between using operator[] and making the
array public. With a public array, clients can bypass FooList's
interface (and FooList's methods) and obtain the data FooList
stores -
directly.

I guess I don't see, in practical terms, the difference. If an address
is returned, without any bounds checking as in my example, then
supposedly one could access any of the data FooList stores and,
consequently, any of the data Foo stores, bypassing FooList's
interface. Or perhaps I'm not understanding. Perhaps an example would
help.

Roger
 
G

Greg

Roger said:
Greg said:
There is a huge difference between using operator[] and making the
array public. With a public array, clients can bypass FooList's
interface (and FooList's methods) and obtain the data FooList
stores -
directly.

I guess I don't see, in practical terms, the difference. If an address
is returned, without any bounds checking as in my example, then
supposedly one could access any of the data FooList stores and,
consequently, any of the data Foo stores, bypassing FooList's
interface. Or perhaps I'm not understanding. Perhaps an example would
help.

Sure, let's have FooList provide the array interface and have no
persistent storage at all:

struct Foo { };

struct FooList
{
Foo* operator[](int index);
};


Foo *FooList::eek:perator[](int index)
{
return new Foo;
}

int main()
{
FooList fooList;

Foo *f1 = fooList[3];
Foo *f2 = fooList[12];
Foo *f3 = fooList[15];
}

Now clients can still "retrieve" Foo objects from fooList - even though
fooList has no array at all - it simply returns a new object at any
index.

A more realistic example would have fooList obtain the objects from
disk or over a network - but the point is that the code in main() can
treat fooList as if it were an array. But fooList does not need to use
an array in its implementation - but can store the objects however it
likes, or, as in this example, not store them at all.

Greg
 
G

Greg

Daniel said:
Bob Hairgrove said:
On Sat, 18 Mar 2006 19:31:25 GMT, "Daniel T."

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>::eek:perator[]). This is probably an example of
encapsulation at its best!

I would like to amend this a little ... of course, for the example
given by the OP, you are correct. I was only trying to illustrate that
an implementation of operator[] can be done in other, non-trivial
ways, and that having an operator[] which returns a non-const lvalue
doesn't necessarily break encapsulation.

But let's also consider that it would be perfectly legal for
operator[] to return a reference to a static object or a dummy member
variable which acts as a proxy for the real array element. One could
then document this fact somewhere so that clients would know not to
attempt to store a pointer or reference to the object.

// assume the methods below update some file or database such that
// assigning a value to foo.bar() calls update and retrieving a value
// from foo.bar() calls get_value()
void update( unsigned id, int i );
int get( unsigned id );

class Foo {
public:
Foo( unsigned id );
~Foo();

int& bar();
};

int main() {
Foo foo1( 1 );
foo1.bar() = 1963;
Foo foo2( 1 ); // note, same ID
assert( foo2.bar() == 1963 );
assert( get( 1 ) == 1963 );
}

Implement Foo however you see fit such that the asserts in main won't
fire...

OK, I did:

#include <map>

class Foo
{
public:
Foo( unsigned id ) : mIndex(id)
{
}

~Foo() {};

int& bar()
{
return implementations[mIndex].value;
}
int mIndex;

struct FooImpl { int value; };
static std::map<int, FooImpl> implementations;
};

std::map<int, Foo::FooImpl> Foo::implementations;

void update( unsigned id, int i )
{
Foo::implementations[id].value = i;
}

int get( unsigned id )
{
return Foo::implementations[id].value;
}

int main()
{
Foo foo1( 1 );
foo1.bar() = 1963;
Foo foo2( 1 ); // note, same ID
assert( foo2.bar() == 1963 );
assert( get( 1 ) == 1963 );
}

Greg
 
G

Greg

Daniel said:
Bob Hairgrove said:
I often see operator[] implemented something like this:

class Foo { ... };

class FooList
{
public:
const Foo& operator[] (unsigned index) const {return
array[index];};
Foo& operator[] (unsigned index) {return
array[index];};
private:
Foo array[num];
};

And this seems natural and intuitive (at least to me). But it seems to
wreck encapsulation. Is there some standard way to avoid this
transgression and still provide the client with an interface that is
natural and easy to use? Or do you just bite the bullet and accept it?
Please pitch responses to someone whose level of knowledge is about
one year of C++ experience.

Why do you say that it wrecks encapsulation? Admittedly, the
implementation shown above doesn't buy you anything over direct public
access to the array member variable, but that doesn't mean that it
can't be done differently.

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.

Of course the client knows that any Foo object that FooList returns is
in memory, because that is exactly where the client (and not the
FooList object) allocated it - and did so before the Foo object was
ever stored in a FooList container. A container is a storage class, it
never "computes" a contained item that it returns, because the items it
contains are all provided by the client.

The Uniform Access Principle is not relevant to containers, because it
applies to a (read-only) attribute of an object. The items that a
container contains are not its attributes (nor are the contents often
read-only) - they are independent objects that can exist (and in fact
are created) outside of any container. In fact a FooList has no
client-accessible attributes which means that it perfectly encapsulates
its internal implementation.

Greg
 
D

Daniel T.

"Greg said:
Roger said:
Greg said:
There is a huge difference between using operator[] and making the
array public. With a public array, clients can bypass FooList's
interface (and FooList's methods) and obtain the data FooList
stores -
directly.

I guess I don't see, in practical terms, the difference. If an address
is returned, without any bounds checking as in my example, then
supposedly one could access any of the data FooList stores and,
consequently, any of the data Foo stores, bypassing FooList's
interface. Or perhaps I'm not understanding. Perhaps an example would
help.

Sure, let's have FooList provide the array interface and have no
persistent storage at all:

struct Foo { };

struct FooList
{
Foo* operator[](int index);
};


Foo *FooList::eek:perator[](int index)
{
return new Foo;
}

int main()
{
FooList fooList;

Foo *f1 = fooList[3];
Foo *f2 = fooList[12];
Foo *f3 = fooList[15];
}

Now clients can still "retrieve" Foo objects from fooList - even though
fooList has no array at all - it simply returns a new object at any
index.

A more realistic example would have fooList obtain the objects from
disk or over a network - but the point is that the code in main() can
treat fooList as if it were an array. But fooList does not need to use
an array in its implementation - but can store the objects however it
likes, or, as in this example, not store them at all.

Minor nit Greg, you changed the signature of op[] to return a pointer
not a reference. Major nit, you changed the semantics of op[], the
defining characteristic of op[] is invalid in your code, namely that
consecutive calls to op[] with the same index will return the same
object.
 
D

Daniel T.

"Greg said:
Daniel T. wrote:

Of course the client knows that any Foo object that FooList returns is
in memory, because that is exactly where the client (and not the
FooList object) allocated it - and did so before the Foo object was
ever stored in a FooList container. A container is a storage class, it
never "computes" a contained item that it returns, because the items it
contains are all provided by the client.

The Uniform Access Principle is not relevant to containers, because it
applies to a (read-only) attribute of an object. The items that a
container contains are not its attributes (nor are the contents often
read-only) - they are independent objects that can exist (and in fact
are created) outside of any container. In fact a FooList has no
client-accessible attributes which means that it perfectly encapsulates
its internal implementation.

By George, I think Greg's got it! This is basically what I have been
saying from the beginning. UAP is broken, but it's OK in some cases...
See my first post in this thread.
 
D

Daniel T.

"Greg said:
Daniel said:
Bob Hairgrove said:
On Sun, 19 Mar 2006 08:36:33 +0100, Bob Hairgrove

On Sat, 18 Mar 2006 19:31:25 GMT, "Daniel T."

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>::eek:perator[]). This is probably an example of
encapsulation at its best!

I would like to amend this a little ... of course, for the example
given by the OP, you are correct. I was only trying to illustrate that
an implementation of operator[] can be done in other, non-trivial
ways, and that having an operator[] which returns a non-const lvalue
doesn't necessarily break encapsulation.

But let's also consider that it would be perfectly legal for
operator[] to return a reference to a static object or a dummy member
variable which acts as a proxy for the real array element. One could
then document this fact somewhere so that clients would know not to
attempt to store a pointer or reference to the object.

// assume the methods below update some file or database such that
// assigning a value to foo.bar() calls update and retrieving a value
// from foo.bar() calls get_value()
void update( unsigned id, int i );
int get( unsigned id );

class Foo {
public:
Foo( unsigned id );
~Foo();

int& bar();
};

int main() {
Foo foo1( 1 );
foo1.bar() = 1963;
Foo foo2( 1 ); // note, same ID
assert( foo2.bar() == 1963 );
assert( get( 1 ) == 1963 );
}

Implement Foo however you see fit such that the asserts in main won't
fire...

OK, I did:

#include <map>

class Foo
{
public:
Foo( unsigned id ) : mIndex(id)
{
}

~Foo() {};

int& bar()
{
return implementations[mIndex].value;
}
int mIndex;

struct FooImpl { int value; };
static std::map<int, FooImpl> implementations;
};

std::map<int, Foo::FooImpl> Foo::implementations;

void update( unsigned id, int i )
{
Foo::implementations[id].value = i;
}

int get( unsigned id )
{
return Foo::implementations[id].value;
}

int main()
{
Foo foo1( 1 );
foo1.bar() = 1963;
Foo foo2( 1 ); // note, same ID
assert( foo2.bar() == 1963 );
assert( get( 1 ) == 1963 );
}

Greg

By storing the value in RAM... This is the only way it can be done and
is my point exactly.
 
D

Daniel T.

"Daniel T. said:
By George, I think Greg's got it! This is basically what I have been
saying from the beginning. UAP is broken, but it's OK in some cases...
See my first post in this thread.

One last nail in the coffin. Don't believe me, believe Scott Meyers. In
"Effective C++" item 29 and 30 he says the same thing in a different
way, "Avoid returning "handles" to internal data." and "Avoid member
functions that return non-const pointers or references to members less
accessible than themselves."

The question in this case is... Are the Foos that a FooList contains
really "less accessible" than the FooList?
 
B

Bob Hairgrove

One last nail in the coffin. Don't believe me, believe Scott Meyers. In
"Effective C++" item 29 and 30 he says the same thing in a different
way, "Avoid returning "handles" to internal data." and "Avoid member
functions that return non-const pointers or references to members less
accessible than themselves."

You said earlier: "returning a reference always breaks encapsulation".
Scott Meyers is saying something entirely different here. As I tried
to point out before, the reference is only dangerous as long as it is
a reference to an object which could change the state of FooList. And
it doesn't have to be. That is why it is important to have an
interface, even if it involves returning a reference -- because it
could be a non-state-changing object to which it refers.

Don Box, in the first chapter of his excellent book "Essential COM",
has pointed out very succinctly the fact that C++ does not provide
encapsulation at the binary level. So the question remains: in what
context do you put the UAP as far as C++ is concerned?
The question in this case is... Are the Foos that a FooList contains
really "less accessible" than the FooList?

They might or might not be. It all depends on the total design. And
that includes the documentation.

Let's talk about documentation. I strongly believe that the
documentation for a class or framework can be just as much a part of
the design as the source code itself. Consider the case of
std::vector. The C++ standard makes the guarantee that on a conforming
implementation, elements of a vector are stored contiguously in
memory. This gives us additional information which could play an
important role in how elements of the vector are accessed. Give me a
pointer or iterator to the first element, and I can access all the
rest. The same could hold true for std::list or whatever other
container is available where iterators are defined.

But this doesn't hold true for vector<bool>. How do I know? The only
reason is that I read about it in the C++ standard. I consider that
"documentation" as well.

As Bjarne Stroustrup says, C++ wasn't designed to be non-hackable.
Things like encapsulation and public/private access were meant to
avoid errors, not to prevent malicious coding. So if what you mean by
adherence to UAP is to avoid the latter, C++ isn't going to do it. And
if it is the former, then we all have to play by the rules -- which
includes reading the documentation.
 
B

Bob Hairgrove

the
defining characteristic of op[] is invalid in your code, namely that
consecutive calls to op[] with the same index will return the same
object.

Who says?
 
N

Noah Roberts

Greg said:
Roger said:
Greg said:
There is a huge difference between using operator[] and making the
array public. With a public array, clients can bypass FooList's
interface (and FooList's methods) and obtain the data FooList
stores -
directly.

I guess I don't see, in practical terms, the difference. If an address
is returned, without any bounds checking as in my example, then
supposedly one could access any of the data FooList stores and,
consequently, any of the data Foo stores, bypassing FooList's
interface. Or perhaps I'm not understanding. Perhaps an example would
help.

Sure, let's have FooList provide the array interface and have no
persistent storage at all:

struct Foo { };

struct FooList
{
Foo* operator[](int index);
};


Foo *FooList::eek:perator[](int index)
{
return new Foo;
}

int main()
{
FooList fooList;

Foo *f1 = fooList[3];
Foo *f2 = fooList[12];
Foo *f3 = fooList[15];
}

Now clients can still "retrieve" Foo objects from fooList - even though
fooList has no array at all - it simply returns a new object at any
index.

A more realistic example would have fooList obtain the objects from
disk or over a network - but the point is that the code in main() can
treat fooList as if it were an array. But fooList does not need to use
an array in its implementation - but can store the objects however it
likes, or, as in this example, not store them at all.

Whereas the original example, which returned references, did not break
encapsulation, this one does.

Foo * f1 = fooList[3];
Foo * f2 = f1 + 2;

This would be ok for a client of the class with an internal array but
not otherwise. The class's interface does nothing to suggest that
would not be valid. On the other hand, returning a reference does. It
says that this is not something that should be considered a pointer so
don't try anything pointerish with it. A client could still do
something similar with references:

Foo & f1 = fooList[3];
Foo * f2 = &f1 + 2;

You can't stop that kind of thing really, unless you return a reference
proxy. There may be reason enough to do so. However, this is
obviously much more hackish and there can be no doubt in the mind of
the developer that they are purposefully breaking encapsulation for
some stupid reason. With a pointer return this is not so obvious.

The preferable interface of course returns copies or proxies that act
like copies...maybe copy on write type of reference counting mechanism.
With a proxy you could even change the semantics of the class to allow
the creation of new Foo's because maybe the Foo is something on some
other server and it needs to be copied. Both references and pointers
pose problems for this need.

About the class not doing any bounds checking, it easily could. There
is nothing saying that operator[] doesn't throw. You could add that
functionality and not hurt clients, assuming they are already exception
safe (and they should be). std::vector doesn't check either but still
offers a lot in comparison to a public array.
 
D

Daniel T.

Bob Hairgrove said:
You said earlier: "returning a reference always breaks encapsulation".
Scott Meyers is saying something entirely different here. As I tried
to point out before, the reference is only dangerous as long as it is
a reference to an object which could change the state of FooList. And
it doesn't have to be. That is why it is important to have an
interface, even if it involves returning a reference -- because it
could be a non-state-changing object to which it refers.

Again, this is exactly my point. If logically, FooList is simply holding
the Foos for some other object and doesn't actually own them, then
breaking UAP is OK, but UAP is still broken. That's why I said to the OP
that although he was right and encapsulation was broken, that isn't
necessarily a problem.

Don Box, in the first chapter of his excellent book "Essential COM",
has pointed out very succinctly the fact that C++ does not provide
encapsulation at the binary level. So the question remains: in what
context do you put the UAP as far as C++ is concerned?


They might or might not be. It all depends on the total design. And
that includes the documentation.

Let's talk about documentation. I strongly believe that the
documentation for a class or framework can be just as much a part of
the design as the source code itself. Consider the case of
std::vector. The C++ standard makes the guarantee that on a conforming
implementation, elements of a vector are stored contiguously in
memory. This gives us additional information which could play an
important role in how elements of the vector are accessed. Give me a
pointer or iterator to the first element, and I can access all the
rest. The same could hold true for std::list or whatever other
container is available where iterators are defined.

But this doesn't hold true for vector<bool>. How do I know? The only
reason is that I read about it in the C++ standard. I consider that
"documentation" as well.

All of this proves my point. The interface gives away the implementation
(its not encapsulated.) That's OK in this case though because the docs
require a particular implementation anyway.

As Bjarne Stroustrup says, C++ wasn't designed to be non-hackable.
Things like encapsulation and public/private access were meant to
avoid errors, not to prevent malicious coding. So if what you mean by
adherence to UAP is to avoid the latter, C++ isn't going to do it. And
if it is the former, then we all have to play by the rules -- which
includes reading the documentation.

Not at all. Nothing can prevent a programmer from hacking into the
memory footprint of an object and access anything he wants. I am talking
about the legions of programmers who provide reference returns and
*think* they are properly encapsulating their data.

As Meyers himself wrote: "Unfortunately, the presence of [a member
function returning a non-const reference] defeats the purpose of making
[a private member-variable] private." (Meyers, "Effective C++" item 30)
 
D

Daniel T.

Bob Hairgrove said:
the
defining characteristic of op[] is invalid in your code, namely that
consecutive calls to op[] with the same index will return the same
object.

Who says?

Herb Sutter, Scott Meyers, Bjarne Stroustrup among others... A common
quote "when using operator overloading or any other language feature for
your own classes, when in doubt always make your class follow the same
semantics as the builtin and standard library types."

If your op[] does not follow the same semantics as op[] does on an
array, then I suggest you rename your function so you won't make life
harder on your users. Remember, operator overloading is exists to make
life easer on the users of your class.
<http://www.parashift.com/c++-faq-lite/operator-overloading.html>
 
B

Bob Hairgrove

Not at all. Nothing can prevent a programmer from hacking into the
memory footprint of an object and access anything he wants. I am talking
about the legions of programmers who provide reference returns and
*think* they are properly encapsulating their data.

So we agree that encapsulation cannot be achieved 100% purely by using
the language features in C++, but only through proper design? If you
agree with that, I still do not understand why you categorically
stated earlier that returning a reference breaks encapsulation?
As Meyers himself wrote: "Unfortunately, the presence of [a member
function returning a non-const reference] defeats the purpose of making
[a private member-variable] private." (Meyers, "Effective C++" item 30)

No. This is more like what he says:

"Unfortunately, the presence of [a member function returning a
non-const reference TO A PRIVATE MEMBER OF THE CLASS WHICH CAN CHANGE
THE CLASS STATE] defeats the purpose of making [THAT private
member-variable] private." (Meyers, "Effective C++" item 30)

The fact that the return value is a reference is irrelevant by itself.
It all depends on what the reference refers to. As I pointed out
before, it could be a reference to some dummy variable which is kept
solely for the purpose of satisfying clients who need some kind of
non-const lvalue to write to. What actually is written (or not, as the
case may be) is solely under control of the class containing the
member and enforced through the implementation of the function
returning the reference.

And even if it is a reference to some meaningful member of the class,
the function can be implemented in a discretionary manner. For
example, operator= often checks for "this==&argument". If the check
proves true, the behavior is different than if it isn't.

Why are you so afraid of references? It's all about design and not
about the language itself. Implementing operator[] as in the OP's
original example DOES break encapsulation. But it can be implemented
differently in the background using the same interface. So it is the
implementation, and not the interface, which makes the difference.
 
B

Bob Hairgrove

Bob Hairgrove said:
the
defining characteristic of op[] is invalid in your code, namely that
consecutive calls to op[] with the same index will return the same
object.

Who says?

Herb Sutter, Scott Meyers, Bjarne Stroustrup among others... A common
quote "when using operator overloading or any other language feature for
your own classes, when in doubt always make your class follow the same
semantics as the builtin and standard library types."

If your op[] does not follow the same semantics as op[] does on an
array, then I suggest you rename your function so you won't make life
harder on your users. Remember, operator overloading is exists to make
life easer on the users of your class.
<http://www.parashift.com/c++-faq-lite/operator-overloading.html>

That seems awfully limiting to me ... look at std::map::eek:perator[],
for example. Does that act like an array??
 
P

Phlip

Bob said:
If your op[] does not follow the same semantics as op[] does on an
array, then I suggest you rename your function so you won't make life
harder on your users. Remember, operator overloading is exists to make
life easer on the users of your class.
<http://www.parashift.com/c++-faq-lite/operator-overloading.html>

That seems awfully limiting to me ... look at std::map::eek:perator[],
for example. Does that act like an array??

That is a good example of the difference between syntax and semantics. Map[]
doesn't use the same syntax, but it gives the same warm-fuzzies. Semantics.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,780
Messages
2,569,608
Members
45,252
Latest member
MeredithPl

Latest Threads

Top