Boost logo

Boost :

From: williamkempf_at_[hidden]
Date: 2001-10-30 10:05:35


--- In boost_at_y..., "Kevin S. Van Horn" <kevin.vanhorn_at_n...> wrote:
> On Mon, 29 Oct 2001 brangdon_at_c... wrote:
>
> > This passes the source range as two objects. I would prefer to
represent a
> > range with a single object, as Sean Parent suggested.
>
> Although I like the idea of having the *option* of passing the
range as a
> single object, making that the only option causes you to lose some
power
> and flexibility. Your one-object-range proposal doesn't allow one
to
> specify a range and a point within that range. I think it is a
mistake to
> lump everything together from the beginning. I prefer the Unix
philosophy
> of flexible combinations of simple tools, and "do one thing and do
it
> well". Conveniences like the single-object range should be built
on top
> of more flexible and simple primitives.
>
> > I think copy should look more like:
> >
> > template <typename InputRange, typename OutputIterator>
> > OutputIterator copy( InputRange src, OutputIterator dst ) {
> > for ( ; !src.empty(); ++src)
> > *dst = *src;
> > }
>
> I strongly object to the "src.empty()" syntax, as it is too
restrictive --
> it forces InputRange to be a class type that already has a
> member function "empty" defined. For example, I couldn't use a
pair of
> iterators as my range. I would prefer an "empty(src)" syntax.
>
> 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. The designs are
in fact the opposite of brittle since full encapsulation is
employed. Further, there's no way you can claim that they expose
implementation decisions since they do exactly the opposite.

The desire, right or wrong, so use the syntax empty(src), does not
impact this design decision in any possible way.

template <typename T>
bool empty(T src)
{
   return src.empty();
}

No encapsulation is fully employed and you get the syntax you want.

> Given a
> choice between the following, I prefer (3) first (when possible),
followed
> by (2) when (3) is not possible:
>
> (1)
> class foo {
> public:
> ...
> void some_operation();
> ...
> };
>
> (2)
> class foo {
> public:
> ...
> friend void some_operation(foo &);
> ...
> };
>
> (3)
> class foo {
> ...
> };
>
> void some_operation(foo &);
>
> The reason is this. In general, we want to limit access to the
internals
> of foo as much as possible. Only functions that really *need*
access to
> the implementation details of foo should be given this access. If
we can
> efficiently implement a function entirely in terms of other, more
> primitive operations on the class, we should do so. Thus, nothing
should
> be a member function unless it needs access to implementation
details.

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.
 
> However, by making some_operation() a member function instead of a
> friend, we are leaking some implementation details: the fact that,
in
> the current implementation, some_operation requires access to the
> internals of foo. The implementation of foo may change in the
future, and
> some_operation() may no longer need access to its implementation
details.
> But if we go with interface (1), we are stuck: once a member
function,
> always a member function, due the the syntactic distinction between
member
> functions (x.some_operation()) and non-member functions
> (some_operation(x)).

I've already shown that this argument is weak. We can trivially
change (actually, just extend) the syntax to allow for some_operation
(x).
 
> 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 for member functions. If member functions simply took the
object
> as a first, reference parameter, there would be no syntactic
distinction
> between member functions and friend functions. If, in
addition, "x.bar"
> and "bar(x)" were considered synonyms, there would be no need for
the
> awkward syntax of accessor functions. You could take any class that
> exposed a public data member bar, and later on decide to replace it
with
> an accessor function without having to rewrite any code that used
the
> class.

Simula is hardly the only OO language that uses this syntax. In
fact, I'm unaware of any OO language that doesn't. 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.

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) then I
still feel strongly that it's wrong to not use standard OO designs in
C++, and I've yet to see an argument for the "free function" design
that's convincing.

> BTW, now that I'm on a related subject, and since there are many
standards
> committe members on this list, I have a proposal for reducing the
leakage
> of implementation details into header files. It is unfortunate
that one
> must list all of the private member functions for a class in the
class
> declaration, as this exposes implementation details in the header
file.
> Is there a way of avoiding this while still protecting the innards
of a
> class from unwanted peeking and probing?

Yes, employ the PIMPL idiom. However, private functions don't truly
expose implementation details, at least not to the extent that
matters. As private functions they ARE implementation details and
are subject to change at any point. Anyone that writes code that
somehow depends on implementation details gleaned from looking at the
private methods or even the private data should be taken out back and
shot twice. Fully hiding such implementation details is "useful" in
two cases: you're trying to hide the implementation from those who
might steal the copyrighted work you've done or you want to reduce
coupling. The first reason is a weak one since it's possible to
reverse engineer the code from the binary. The second usually has
more to do with private data then with private methods and there's
usually ways to reduce dependencies with out totally removing these
implementation details from the header. When you can't and it's
truly important the PIMPL idiom will always work.

Bill Kempf


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