Boost logo

Boost :

From: williamkempf_at_[hidden]
Date: 2001-10-31 14:12:40


--- In boost_at_y..., "Kevin S. Van Horn" <kevin.vanhorn_at_n...> wrote:
> On Tue, 30 Oct 2001 williamkempf_at_h... wrote:
>
> > --- In boost_at_y..., "Kevin S. Van Horn" <kevin.vanhorn_at_n...> wrote:
> > > I'm going to go out on a limb here and say that I am
increasingly
> > > of the opinion that non-static member functions are usually a
bad idea, as
> > > they produce brittle designs and expose implementation
decisions.
> >
> > OO experts will strongly disagree about all of this.
>
> Of course, the priests of any religion will disagree when you
challenge their
> orthodoxy. Not that I'm against OO in general. It's just that I
think a lot
> of OO advocates go overboard and think that their particular notion
of OO is
> the one and only true path to programming perfection.

That really doesn't apply here, however. You're simply arguing for a
different religion (generic coding).

> [I discussed Three possibilities: (1) public member function; (2)
friend
> function; (3) non-member, non-friend function. I stated that I
prefered (3)
> when possible, and lean toward (2) over (1). The reason is to
limit access
> to the internals of a class to only those functions really needing
it.]

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.

(2) on the other hand should never be prefered. Friend functions are
fragile and can open the code up to abuse. They also clearly
illustrate that the above principle wasn't adhered to. This is
precisely the problem I have with the desire of those following
the "generic" religion to make all functions free functions.
 
> > In general I'd agree. However, the problem with this reasoning
> > becomes appearant when the internals change making the non-member
> > function implementation overly innefficient in comparison to an
> > equivalent member function.
>
> That's when you go from (3) to (2). Member functions have no
performance
> advantage whatsoever over friend functions.
>
> [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.]

This argument is, again, very weak IMHO. I see nothing wrong with
the function remaining a member function, and no compelling argument
has ever been given as to why you should find this to be bad.
 
> > I've already shown that this argument is weak. We can trivially
> > change (actually, just extend) the syntax to allow for
some_operation
> > (x) [instead of x.some_operation()].
>
> You miss the point entirely.

No, I really didn't.

> Sure, you can always define a non-member
> function that is just syntactic sugar on top of the member
function. What
> you can't do is prevent people from using the *public* member
function. 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()?
2) some_operation() already *IS* a non-member (as well as a member)
so this is a non-issue.

The arguments here are purely about syntax. In general I believe
most folks are going to prefer the x.some_operation() syntax as this
is the syntax used by (nearly?) all OO languages. The only
compelling argument for the some_operation(x) syntax is for use in
generic algorithms, but if both forms of the syntax are provided this
becomes a non-argument. Sorry, you've not provided ANY compelling
arguments for changing the "normal" syntax here.
 
> > > The problem would be resolved, of course, if it were not for the
> > > syntactic flaw that C++ inherited from Simula of using the
awkward
> > > object.function() syntax [...]
> >
> > Simula is hardly the only OO language that uses this syntax.
>
> It was the first.
>
> > You can argue that maybe the syntax isn't optimal, but it is the
way it is
> > and it's what OO designers expect. So the argument is pointless.
>
> No, it's not pointless, as there will be new languages defined in
the future,
> and it would be well if the designers of those languages would
think about
> these issues instead of thoughtlessly repeating a mistake that is
now decades
> old.

It's not a mistake, however. The only existing language for which
any argument can be made for this syntax being a mistake is C++.
It's unique in this regard because it has both generics and free
standing functions. Eiffel, for instance, has generics but no free
standing functions so the syntax x.some_operation() (not quite the
syntax used by Eiffel, but lets not get bogged down here) is fully
well thought out.

As for C++ itself, I still see no compelling reason. None. A simple
wrapper will adapt the member function approach for generic
algorithms which expect the free function approach.

> > Templates and generic programming have lead to some slightly
> > different views on design, but unless a concept is specifically
meant
> > to be used by generic algorithms (range may be such a concept)
>
> I think you have this all wrong. You have to assume from the
beginning that
> anything might be a candidate for generalization, if not now then
in the
> future, and therefore one should avoid unnecessary semantic and
syntactic
> diversity. For example, if C++ had insisted on using different
> operators for float addition, integer addition, and double
addition, generic
> numeric algorithms would be substantially harder to write and
understand.
> (I will admit that there are strong arguments for syntactically
distinguishing
> integer divide and floating-point divide, as there are important,
necessary
> semantic differences between the two.) My biggest beef against
Java is the
> great semantic and syntactic gulf between primitive data types and
> user-defined types. This nonuniformity torpedoes any possibility
of full
> generic programming in Java, even if templates were added to the
language, and
> makes even limited generic programming very awkward and difficult.

The language syntax was designed before generics existed. For this
reason it has a heavy bias towards the x.some_operation() syntax.
Generic algorithms have a choice for supporting either syntax, and
often it will be appropriate for them to use the x.some_operation()
syntax. When an algorithm does use the some_operation(x) syntax I
can trivially provide an adapter. So I find it to simply be wrong to
program the interface assuming a generic usage. It makes the design
more fragile while not giving me anything I can't get with the more
traditional design. So, again, I've yet to see a compelling argument
against this.

> > then I still feel strongly that it's wrong to not use standard OO
designs in
> ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
> > C++,
>
> That sounds a little dogmatic to me. I believe as strongly as
anyone in the
> importance of encapsulation and abstraction; I just don't have the
true
> believer's unquestioning faith that current OO prescriptions are
necessarily
> the best way of achieving those goals. Allow me to point out that
the entire
> field of generic programming -- and much of the current vitality of
C++ --
> arose because Stepanov, Musser, and others weren't willing to stick
to
> "standard OO designs" in the quest for generality and code reuse.

Yes and no. Notice that the containers have always been designed
with traditional OO principles (i.e. member functions instead of
friends). Adapters of sorts (the iterators) were used to bridge from
the OO paradigm to the generic algorithm paradigm. I believe that
this was the appropriate design choice, and is consistent with what
I've been saying here all along. C++ is unique among languages in
that it allows us to program to multiple paradigms (OO, procedural,
generic, interface, etc.). You have to carefully evaluate which
paradigm is appropriate for a given task and write code that bridges
between the various other options. 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.

> > I've yet to see an argument for the "free function" design
> > that's convincing.
>
> I've yet to see any argument that the syntactic distinction between
member and
> nonmember functions serves any useful purpose.

That's a different argument, and simply doesn't apply since C++ does
distinguish between the syntaxes. Trying to work around this through
kludges (making functions friend instead of member) simply isn't
appropriate. Don't argue for the syntax, because we can't change
that, but argue for the design.

> As argued above, we should
> strive for syntactic and syntactic uniformity as the norm,
departing from it
> only with good reason and to the extent necessary. The difference
between a
> "free function" (using friends) and member function design is purely
> syntactic.

No, it's not purely syntactic. Differences:

1) Friends are fragile and are open to abuse.
2) Free functions pollute the namespace.
3) Free functions don't clearly indicate the coupling to the object.

Since they simply do not buy you anything I can not agree with a
desire to use friend functions instead of members.

> From a standpoint of conceptual economy, support for generic
> programming, and flexibility to grant or deny access to the innards
of a
> class, I prefer the uniform syntax.

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 ;). (This is said only half facetiously to
illustrate the religous bias being shown.)
 
> > > [Proposal to avoid the need to declare private member functions
in
> > > public header files.]
> >
> > [...] employ the PIMPL idiom.
>
> That works sometimes, but what if you want a lightweight class and
don't want
> to be doing dynamic memory allocation all the time?

There are often ways to achieve this. Granted, not always, but then
I often question the desire to hide things to this extent any way.
 
> > However, private functions don't truly
> > expose implementation details, at least not to the extent that
> > matters.
>
> I'm more concerned with unnecessarily large header files and having
to
> recompile all source files that use a class just because I
restructured the
> class implementation code to factor out some common functionality.

Header decoupling. I wish there was a universal solution to this in
C++, but I don't know of any. The "friend" approach isn't going to
save you here. The only approach that will is the PIMPL idiom, but
as you pointed out this is often a rather runtime-expensive solution.

Bill Kempf


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