Topic : A Critique of C++
Author : Ian Joyner
Page : << Previous 7  Next >>
Go to page :

creation of a temporary is implementation
dependent, so might or might not be done.  If a temporary is created, a
constructor is called as a side effect, which can change the state of
the object.  Different C++ implementations could therefore return
different results for the same code.


3.12.  Optional Parameters

   Optional parameters that assume a default value according to the
routines declaration are supposed to provide a shorthand notation.
Shorthand notations are intended to speed up software development.  Such
shorthand notations can be convenient in shell scripts, and interactive
systems.  In large scale software production, however, precision is
mandatory, and defaults can lead to ambiguities and mistakes.  With
optional parameters the programmer could assume the wrong default for a
parameter.  More importantly, optional parameters undermine type safety.
The type of a function is defined by the composition of its input types,
and its output type:

   f: T1 x T2 x T3...  -> T4

   The entire signature determines the type of the function, not just
the return type.  Optional parameters mean that C++ is not type safe,
and that the compiler cannot check that the parameters in the call
exactly match the function signature.

   Furthermore, they do not provide a great deal of convenience.  If a
routine has five parameters, the last three of which are optional, and
caller wants to assume the defaults for parameters 3 and 4, but must
specify parameter 5, then all five parameters must be specified.  A
better scheme would be to have a 'default' keyword in function calls:

   f (a, b, default, default, e);

   Other means, already in the language, can easily provide this
mechanism.  For example, a call to another (possibly inline) function
could provide the defaults for the optional parameters.  This not only
provides the convenience of optional parameters, but is more powerful.
Any parameter or combination can be filled in with any combination of
defaults, not just the last parameters.  Multiple intermediate routines
can provide multiple sets of defaults.


3.13.  Bad deletions

   The following example is given on p.63 in the C++ ARM as a warning
about bad deletions that cannot be caught at compile-time, and probably
not immediately at run-time:

   p = new int[10];
delete p; // error
p = 0;
delete p; // ok

   One of the restrictions of the design of C++ is that it must remain
compatible with C.  This results in examples like the above, that are
ill-defined language constructs, that can only be covered by warnings of
potential disaster.  Removal of such language deficiencies would result
in loss of compatibility with C.  This might be a good thing if problems
such as the above disappear.  But then the resultant language might be
so far removed from C that C might be best abandoned altogether.


3.14.  Local entity declarations

   Declaring an entity close to where it is used, has both advantages
and disadvantages.  It is convenient, but can make a routine appear more
complex and cluttered.  A problem is that an identifier can be
mistakenly overloaded within a nested block in a function, with the
resultant problems covered in the sections on name overloading and
nesting.  C does not have nested routines or blocks so does not have
this problem.  ALGOL uses this simple form of name overloading.  (A
block in the ALGOL sense contains both declarations and instructions.)

   The ARM explains problems of local declarations with branching, which
shows the complications in intermingling declarations and instructions.
Caveats cannot make up for or fix faulty language definition.

   The C++ FAQ [Cline] (Q83) is unclear on this point (although it is
mostly excellent), claiming that an object is created and initialised at
the moment it is declared.  This only applies to auto, in stack objects.
Dynamic entities are not created and initialised until they are the
subject of a 'new' instruction.  In well written object-oriented
software, routines will be small, typically performing one atomic action
per routine.

   Small routines that implement atomic operations are fundamental to
loose coupling.  For example, a base class that provides a single
routine that logically performs operations A and B, is not useful to a
subclass that needs to provide its own implementation of B, but does not
want to change A.  The descendant must reimplement the logic of both A
and B, missing an opportunity to reuse the logic of A.  Tight coupling
reduces flexibility.  Splitting A and B into different routines
accomplishes loose coupling, and therefore flexibility.  Efficiency is
also attained without the mess of local entity declarations.  Good
design and clean modularisation achieve efficiency, as the entities
which would be locals to a block in C++ are only created when the
routine is entered.


3.15.  Members

   Care should be taken with the C++ use of the term member.  In general
use, an object is a member of a class.  This corresponds to members in
set theory.  But in C++, the term member means a data item, or function
of the class.  This ambiguity could have easily been avoided.


3.16.  Friends

   Friends are a mechanism to override data hiding.  Friends of a class
have access to its private data.  Friend is a 'limited export'
mechanism.  Friends have three problems:

   1) They can change the internal state of objects from outside the
definition of the class.
   2) They introduce extra coupling between components, and therefore
should be used sparingly.  3) They have access to everything, rather
than being restricted to the members of interest to them.

   Friends are useful, and a case can be made for shades of grey between
public, protected and private members.  Multiple interfaces to a class
provide the functionality of friends and avoid the above problems.  Each
interface to the class can be exported to everything, or selected
classes only.  A selective export mechanism is more general than public,
private, protected and friend, and explicitly documents the couplings
between entities in the system.  Selective export specifies not only
that a member is exported but to which classes it is exported.

   One reason given for friends, is that they allow more efficient
access to data members than a function call.  The way C++ is often used
is that data members are not put in the public section, because this
breaks the data hiding principle.

   Data hiding is better described as 'implementation hiding'.  Only a
classes abstract functional interface should be visible to the outside
world.  That is data members can be exported, but are viewed externally
as functional entities.  This is because, when used in expressions,
functions and variables have no semantic difference.  They both return
values of a given type.  (See fn () for an explanation of why variables
and functions are best regarded as similar entities.) (See also Marshall
Cline's explanation of friends in the FAQ for further clarification of
the friend concept.)

   The Cambridge Encyclopedia of Language has an interesting point about
public and private names.  It says "Many primitive people do not like to
hear their name used, especially in unfavourable circumstances, for they
believe that the whole of their being resides in it, and they may
thereby fall under the influence of others.  The danger is even greater
in tribes (in Australia and New Zealand, for example), where people are
given two names - a 'public' name, for general use, and a 'secret' name,
which is only known by God, or to the closest members of their group.
To get to know a secret name is to have total power over its owner."


3.17.  Static

   The word 'static' is confusing in C++.  Page 98 of the C++ Annotated
Reference Manual (ARM) mentions this confusion and gives two meanings.
Firstly, a class can have static members, and a function can have static
entities.  The second meaning comes from C, where a static entity is
local in scope to the current file.  The choice of different keywords
would easily solve this trivial problem.  There is also a third more
general meaning that objects are statically or automatically allocated
and deallocated on the stack when a block is entered and exited, as
opposed to dynamically allocated in free space.

   Static class members are useful.  Page 181 of the ARM states that
statics reduce the need for global variables.  It is good to reduce
global variables, but the C syntax obscures the purpose.

   Entities declared in functions can also be static.  These are not
needed in an object-oriented language.  The reason and history is this.
ALGOL has the notion of 'OWN' locals in blocks.  The semantics of an OWN
entity is that when a block is exited, the value of the OWN is preserved
for the next entry to the block.  I.e.  the value is persistent.  The
implementation is that at compile time, the OWN entity is limited in
scope to the block, but at run time,

Page : << Previous 7  Next >>