Boost logo

Boost :

From: Kevin S. Van Horn (kevin.vanhorn_at_[hidden])
Date: 2001-10-29 18:52:07


On Mon, 29 Oct 2001 brangdon_at_[hidden] 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. 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.

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)).

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.

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? I believe so. The rule I
propose is this:

1. From the standpoint of a class, all functions can be divided into three
   groups:
   a. public member functions / friend functions;
   b. functions that are neither public members nor friend functions, yet
      still have access to the innards of the class;
   c. all others.

2. Only group (a) need be declared in the class declaration.

3. Only functions in group (a) and (b) can call functions in group (b) or
   create pointers to functions in group (b).

4. main() is always in group (c).

The functions in group (b) replace private member functions. One might
think that this opens up an encapsulation hole, since users of the class
could define new functions of group (b) to their hearts' content. However
-- THIS IS THE IMPORTANT POINT -- although they can define such functions,
they can't ever cause such functions to be called! Calls to functions of
group (b) ultimately arise only from calls emanating within functions of
class (a).

I haven't yet thought about how protected functions would fit into this
scheme.


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