$include_dir="/home/hyper-archives/boost/include"; include("$include_dir/msg-header.inc") ?>
From: williamkempf_at_[hidden]
Date: 2001-11-01 17:55:17
--- In boost_at_y..., "Kevin S. Van Horn" <kevin.vanhorn_at_n...> wrote:
> On Wed, 31 Oct 2001 williamkempf_at_h... wrote:
> 
> > > [My description of "minimal but complete" class interfaces.]
> > There's a reason to prefer (3) in a general sense.  I forget the 
OO
> > principles name, but in essence the public interface should
> > be "minimal but complete".  This principle can be taken too far,
> > however.
> 
> Would you care to elaborate?  What do you consider taking it too 
far, and what
> criteria do you propose for when one should violate the principle?
> 
> > > [I discussed the problem with (1): Once a member function, 
always a
> > > member function, even if in a later implementation the function 
doesn't
> >   need access to the class internals.]
> >
> > [...]  I see nothing wrong with
> > the function remaining a member function,
> 
> It's called *encapsulation*, something that OO enthusiasts claim to 
be in
> favor of.
Actually, that's a perversion of the term you're using.  Yes, there's 
an OO principle that tells you to decouple dependencies when ever 
possible, and this leads directly to the "minimal but complete" 
principle.  But lumping these principles under the guise 
of "encapsulation" is awfully misleading.
>  Let me start from the beginning again, since I don't seem to be
> communicating my point well.  If you disagree with my analysis, 
please make
> clear the specific point or points you disagree with and why.
> 
> 1. You make your code easier to maintain and more reliable by 
decoupling
> different pieces of code as much as possible.  That is, you want the
> correctness of code piece A to depend as little as possible on the 
details of
> code piece B, and vice versa.  This allows you to localize 
modifications to
> the code.  As someone who's spent many a tedious hour grepping 
through source
> code to make one small change to an ill-encapsulated code base 
inherited from
> others, I am a big, *big*, BIG fan of encapsulation.
Other than my disagreement with your usage of the term encapsulation 
we're in agreement on this one.
 
> 2. One important technique for decoupling different pieces of code 
is to not
> allow any function to have access to the innards of a class unless 
it really
> needs access.  Letting it have access means that you have to review 
the
> function definition when changing the class implementation; 
furthermore,
> functions that have access to a class's innards tend to become 
reliant on
> implementation details of the class, and require updating 
themselves when the
> class implementation changes.
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.
 
> 3. There are two kinds of functions that have access to the innards 
of a C++
> class: member functions and friend functions.  Thus, a function 
should not be
> a member or friend function unless it really needs access to class
> implementation details.
You're starting to go down the wrong road here by mentioning 
friends.  I know this only because of previous discussions on this 
topic, but your use here is screaming at me.
 
> 4. If the implementation of a class changes and a friend function 
no longer
> requires access to the class innards (you can efficiently implement 
it on top
> of other operations), it is easy to deny the function access to the 
class
> innards -- just remove the friend declaration.  This does not 
change the user
> interface and does not break any existing code that uses the class.
This just causes maintenance problems.  You could easily find 
yourself switching back and forth between friend and non-friend 
implementations.  However, if you take the approach I've suggested 
instead this is a non-issue.  You make your best guess as to whether 
a method should be free standing or a member, and if you find you 
made the wrong choice in the future you simply add the version that's 
missing and your maintenance woes are finished with out breaking any 
client code.
 
> 5. If the implementation of a class changes and a member function 
no longer
> requires access to the class innards (same reasons), you are now 
forced to
> break encapsulation principle (2).  You cannot change the member 
function into
> a non-member function, as this changes the syntax by which it is 
called and
> would break existing code using the class.  The member function 
must remain a
> member function, which means that you have to give it access to the 
class
> innards even though it doesn't need this access.  Your design is 
now less
> encapsulated than it should be.
> 
> > > Once a function is declared as a member function, there's 
nothing you
> > > can do to stop library users from writing x.some_operation() 
instead of
> > > some_operation(x), and hence you cannot later make 
some_operation()
> > > a non-member without breaking existing client code.
> >
> > 1)  Why should you prevent them from writing x.some_operation()?
> 
> As described in point 5 above, any such use forces some_operation 
to remain a
> member function, which means that it will forevermore have access 
to class
> implementation details, even if it doesn't need them, resulting in 
an
> encapsulation violation.
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.  You don't know, nor should you care, if 
x.some_operation() requires access to internal details of the class.  
This allows the class implementer to change the implementation, which 
may change whether or not x.some_operation() needs access, with out 
any impact to client code and with minimal impact to maintenance.
So, these are the rules I live by.
1)  If you need a method requires access to implementation details of 
the class make it a member.
2)  If you know that a method could be more efficient if it had 
access to implementation details of the class make it a member.
3)  If you are uncertain if a method will be more efficient if it has 
access to implementation details of the class make it a non-member.
4)  If you later find that a non-member could be more efficient if it 
had access to implementation details of the class add a member and 
have the non-member call it.
5)  If you find you need a free function in order to work with a 
generic algorithm you simply create the adapter.
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).
> > Generic algorithms have a choice for supporting either syntax, and
> > often it will be appropriate for them to use the x.some_operation
()
> > syntax.
> 
> The code of the generic algorithm itself should not use the 
x.some_operation()
> syntax, as this limits the algorithm to only be applicable to data 
types which
> are classes providing some_operation() as a member function, and 
rules out the
> possibility of adapting other types for use with the algorithm.
This is simply wrong.
template <typename T>
void call_foo(T o, bar b)
{
   o.foo(b);
}
void foo(bar b);
class foo_adapter
{
public:
   void foo(bar b) { ::foo(b); }
};
call_foo(foo_adapter(), bar());
Adapters freely allow you to change the interface in any way you need 
to.
> > This means that I find it flatly
> > wrong to avoid member functions simply because there might, maybe,
> > someday, be a generic algorithm which is going to use a free 
standing
> > method instead.
> 
> That's not my reason for avoiding member functions, although it can 
be a
> reason for wanting at least a non-member wrapper function.  Points 
1-5 above
> are my reason for avoiding member functions.  Even if you don't 
think 4 and 5
> happen often enough to be important, points 1-3 (a distillation of 
Scott
> Meyers' arguments) are still a strong argument for avoiding member 
functions
> when you don't need access to the implementation details of a class.
Again, I don't have problems with free standing functions, but *only 
when they do not need access to class internals*.  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).
> > 3) Free functions don't clearly indicate the coupling to the 
object.
> 
> And just what does "coupling to the object" mean?  The fact that 
the object is
> being modified?  Not all member functions modify the object.
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.
 
> > If it were purely syntax you'd have more of an argument, but it's
> > not.  In any event, I prefer the x.some_procedure() syntax any 
way,
> > so I'd argue that if you want uniformity we should make everything
> > follow this syntax ;).
> 
> No, 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 in OOSC2, and his 
arguments are well reasoned and were considered *before* the syntax 
was chosen for Eiffel.  So at best you're entering into a religious 
war here.
 
> > So I find it to simply be wrong to
> > program the interface assuming a generic usage.  It makes the 
design
> > more fragile [...]
> >
> > 1) Friends are fragile and are open to abuse.
> 
> Explain to me how use of friends makes the design fragile.  Explain 
to me how
> friends are *more* open to abuse than member functions, as both 
must be
> declared in the class declaration.
Fragile because many compilers have problems implementing them and 
because the violation of encapsulation causes maintainance problems.  
I probably spoke too quickly when I mentioned being open to abuse 
because most of the flaws here apply to friend classes and not friend 
functions.
 
> > 2) Free functions pollute the namespace.
> 
> That's why we have overloading.  There may be some implications in 
the
> interaction of member functions vs. non-member functions in C++ 
overload
> resolution that would result in ambiguities for the latter but not 
for the
> former.  This would be a valid argument in favor of the member 
functions, and
> my uncertainties on this point are the one reason I am still only 
considering
> the friend-over-member approach.  If you have specific examples of 
where the
> non-member syntax would cause problems but the member syntax does 
not, I would
> like to see them.
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).  Overloads still 
pollute the namespace they are declared in and can easily lead to 
ambiguities in numerous ways.
Bill Kempf