D
Daniel T.
Bob Hairgrove said:So we agree that encapsulation cannot be achieved 100% purely by using
the language features in C++, but only through proper design?
I don't agree with the above. No design can achieve encapsulation 100%
because the language features in C++ allow intentional break-ins.
A prime example:
class Foo {
int bar;
public:
Foo(): bar( 0 ) { }
int getBar() const { return bar; }
};
int main()
{
Foo f;
std::cout << f.getBar() << '\n';
int* b = reinterpret_cast<int*>( &f );
*b = 5;
std::cout << f.getBar() << '\n';
}
The above is what Stroustrup &al. was talking about.
If you
agree with that, I still do not understand why you categorically
stated earlier that returning a reference breaks encapsulation?
Because a reference return breaks the UAP. The client knows that the
value returned is stored in RAM somewhere, and not computed on the fly.
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.
class Foo {
public:
Foo();
int& getBar();
};
int main()
{
Foo f;
assert( f.getBar() == 0 );
int& b = f.getBar();
b = 5;
assert( f.getBar() == 5 );
}
If the above doesn't work (ie if the last assert fires,) then getBar was
implemented incorrectly, if the assert doesn't fire, then encapsulation
was broken. I don't see why that is so hard for you to grasp...
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.
Please Bob, I'm not afraid of references, I use them whenever
appropriate. It is sometimes appropriate to break encapsulation, and
therefore it is sometimes appropriate to return references.