Boost logo

Boost :

From: Kevin S. Van Horn (kevin.vanhorn_at_[hidden])
Date: 2001-11-02 15:37:12


Since I don't seem to be explaining myself very well, let me start off by
recommending Scott Meyers' article, "How Non-Member Functions Improve
Encapsulation," C/C++ Users Journal, Feb. 2000; you can read it online at
http://www.cuj.com/articles/2000/0002/0002c/0002c.htm. He explains a lot of
these ideas better than I do.

On Thu, 1 Nov 2001 williamkempf_at_[hidden] wrote:

> > [I discuss avoiding giving functions access to class innards if they
> > don't need it.]
>
> And again I agree with your principles. However, I think you and I
> disagree at least somewhat on when methods really need access to
> implementation details of the class. This is just a feeling from the
> discussion, but I believe you and I disagree about when performance
> concerns constitute a need here.

No, I think we agree here. (But, apparently, we disagree about whether we
agree or disagree ... :-) If a function can't provide acceptable performance
without access to implementation details of the class, this constitutes a
valid need for access. My only reservation would be to avoid going overboard
in pursuit of illusory or insignificant performance gains. Remember
Dijkstra's dictum: "Premature optimization is the root of all evil."

> > 4. If the implementation of a class changes and a friend function
> > no longer requires access to the class innards [...] it is easy to deny
> > the function access [...] just remove the friend declaration. [...]
>
> This just causes maintenance problems. You could easily find
> yourself switching back and forth between friend and non-friend
> implementations.

I don't see how adding or removing a friend declaration constitutes a
maintenance problem when you are already altering the function's
implementation.

> Ahh, so that's how your perverting encapsulation. You're taking it
> to mean that if a method doesn't need access to implementation
> details it simply shouldn't be given access. But that isn't the
> purpose of encapsulation. The purpose of encapsulation is to *hide*
> the implementation details.

This isn't a "perversion", it's just logically extending the concept to its
broadest possible application. I'm not just hiding implementation details
from users of a class; I'm also hiding implementation details from all
functions in the class interface that don't need those details.

Let me give you an example of the need for this. One library I'm familiar
with has classes with over 100 member functions, because the designers wanted
to provide a rich interface and were also steeped in the OO cultural bias
towards member functions. I don't know how anyone can say that a class can be
consider well-encapsulated when there are 100+ functions that have access to
its innards; any modification of the class implementation requires examining
every single one of those 100+ functions! One might say that, while these
classes are properly modest outside of a certain social circle, they are
quite promiscuous, displaying their private parts freely, within that rather
broad social circle.

> 6) You always avoid friends unless there's no other efficient
> solution as they are a kludge to work around encapsulation which lead
> to code that's hard to maintain (this is especially true for friend
> classes).
> [...]
> What I disagree with, very strongly, is your choice to use friend functions
> instead of member functions. The only reason for such a choice is to allow
> for usage with generic algorithms. That's a poor defense for
> weakening encapsulation (which is what friend does).

If we were talking about *users* of a class modifying the class declaration
and adding friend functions willy-nilly, in an effort to get at class
internals, I would agree with you. Of course, that's effectively what many OO
programmers do, except that they do it with member functions. That is, if the
class is "owned" by their organization and they need to define some function
operating on the class, they will edit the class declaration and add a new
member function, even when the new function can be efficiently implemented
entirely in terms of the existing class interface. Another common scheme is
to derive a subclass and define the new function as a member of the subclass,
taking advantage of the fact that the class author made the class internals
protected instead of private in a misguided effort to allow for inheritance.
I'm not claiming that these are things an expert OO programmer would do, merely
that these are common violations of encapsulation, and they happen even with
member functions.

The point is, the friend functions I am advocating are declared by the class
designer *as part of the class interface*. Thus, they have precisely the same
effect on encapsulation as defining a member function -- both allow a function
that is part of the class interface to have access to class internals, and
deny access to functions that are not part of the class interface. Herb
Sutter discusses this notion of friend functions as part of a class's
interface in more detail in his book, _Exceptional C++_.

> > > 3) Free functions don't clearly indicate the coupling to the object.
> >
> > And just what does "coupling to the object" mean? [...]
>
> As you yourself pointed out, it's the maintenance coupling that
> matters the most. If you change the implementation of a class you
> need to know which functions this can impact.

And friend functions are declared right in the class declaration, just the
same as member functions. Friend functions and member functions are identical
with respect to this issue. Either way, you just look over the class
declaration to find out what functions you have to review when you change the
implementation.

> > uniformity would argue against using x.some_procedure() for all
> > functions, as it treats the first argument (x) differently from the
> > others, even when the arguments are symmetrical. Java's clumsy a.eq(b)
> > (as opposed to a = b or a == b) is an example.
>
> Bertrand Meyers takes strong exception to this

What? Symmetric notation?

> in OOSC2,

I don't know what this is. Could you provide your own summary of his
arguments, or a URL where I can read his arguments?

> > Explain to me how use of friends makes the design fragile.
> [...]
> Fragile because many compilers have problems implementing them

For templates that may have been a valid concern, but it's a problem that is
rapidly disappearing, if indeed it is still a problem.

> and because the violation of encapsulation causes maintainance problems.

Whether you provide a class interface through friend or member functions, the
effect on encapsulation -- the hiding of class details from application code
(your definition) -- is exactly the same.

> > > 2) Free functions pollute the namespace.
> >
> > That's why we have overloading. [...]
>
> Overloading does not and can not replace namespace protection
> (whether through the namespace keyword or through the more generic
> usage of this term that applies to class members).

So use C++ namespaces to reduce possible conflicts with friend functions.

> Overloads still pollute the namespace they are declared in and can easily
> lead to ambiguities in numerous ways.

I'd still like to see some examples. You might convince me on the
"ambiguities" issue (for C++, at least) if I see some good examples.
On the other hand, I might instead find some good design rules for friend
functions to prevent the ambiguities :-).

BTW, I'm sure you're aware that there are a few cases where friend functions
are clearly to be preferred over member functions: relational
and arithmetic operators, where use of friend functions allows symmetrical
conversion of *both* operands. (I believe Scott Meyers' article discusses
this.)


Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk