From: ian@syacus.acus.oz.au (Ian Joyner)
Subject: C++ Critique (2 of 4) (long)
Summary: Part 2 of C++ critique
Organization: ACUS Australian Centre for Unisys Software, Sydney
Date: Wed, 8 Apr 1992 11:52:59 GMT

(C++ Critique part 2)

Pure Virtual Functions
----------------------

As mentioned above, pure virtual functions provide a means of leaving a function
undefined and abstract, which also means, that a class with such an abstract
function may not be directly instantiated, as the function must be defined in a
descendant class.  The concept is quite alright, but the syntax to express this
concept illustrates where C++ misuses mathematical notation to express a natural
concept.  The syntax is -

	virtual void fn () = 0;

This syntax on the face of it leaves the reader to guess as to what its meaning
is, even those well versed in object-oriented concepts.  The choice of designing
this construct is to use some indirect mathematical construct as C++ has done,
or to use a keyword, which directly expresses the concept.  Direct expression of
concepts enhances communication, and the ease with which a language may be
learnt.  The C++ decision is in keeping with the C philosophy of avoiding
keywords, but often this avoidance is at the expense of clarity.  This concept
would have been more clearly implemented by a keyword, such as -

	pure virtual void fn ();

or

	abstract void fn ();

A suggestion that the mathematical notation makes is that values other than zero
may be used.  What if 13 is equated to the function?
-

	virtual void fn () = 13;

A simple suggestion to fix this is to define '= 0' as abstract:

	#define abstract = 0

then
	virtual void fn () abstract;


Virtual Classes
---------------

If class D multiply inherits class A from classes B and C, then if it only wants
to inherit a single copy of class A, the inheritance of A must be specified as
virtual in both B and C.  Two questions arise from this.  Firstly what happens
if A is only declared virtual in only one of B or C, and secondly, supposing a
class E wants to inherit A via B and C by a different policy, ie multiple copies
of A, than in D.  In C++, the virtual decision must be made early, reducing the
flexibility which may be required in the assembly of derived classes.  In a
shared software environment, as classes B and C may be provided by different
suppliers, it should be left to the implementer of class D or E, exactly how to
resolve this problem.  Such flexibility is key to reusable software.  In
principle you cannot envisage when designing a base class all the possible uses
in derived classes, and the attempt to do so considerably complicates design.  C
is supposed to trust programmers, and in unimportant ways gives them
flexibility, but the design of C++ restricts flexibility where it actually is
important, especially in the object-oriented sense. 


'::', '.'  and '->'
-------------------

While the '.' and '->' syntax came from C structures, it is heavily used in C++
in accessing class members, and illustrates where the C base adversely affects
the flexibility required of an object-oriented language.  When the programmer
declares an entity, a decision is made about how the entity will be accessed. 
The declaration decides whether the entity is accessed directly, or via a
pointer.  When members of the entity are accessed, the programmer should not be
required to know how exactly to access them, but should access them in an
implementation independent fashion, without the need to know whether the entity
is being accessed directly, or via a pointer.  This is because the compiler can
determine exactly how to access the entity. 

In more detail, C++ x.y means access the member y directly in the structure or
object x, whereas x->y (or the equivalent *x.y) means access the member y via
the pointer x.  One of the key concepts of object-oriented programming is that
access to entities is independent of implementation.  The realisation of
implementation independence is a simple matter for compiler technology, as the
compiler knows from the declaration of 'x', whether x is an object itself, or a
pointer to an object.  The compiler may therefore be relied upon to generate the
correct access code.  Thus only one access operator is required in a high level
language, not both '->' or '.'.  This illustrates why declarations are important
for implementation independence, and why type safety is also important. 

A further problem is that the distinction reduces flexibility in the software
fabrication process.  If it is required to change an objects status from via a
pointer to direct, all '->' dereferences must be changed to simple '.'.  This
discourages the optimisation of changing an entity to direct access, although
the correction of changing a direct access to access via a pointer may be
forced. 

For declarations, C++ requires the use of the '::' form.  Most languages use one
syntax (typically the dot) for both declarations and access.  '::' is also
defined as the scope resolution operator.  (Interestingly, in an early paper,
'Classes: An Abstract Data Type Facility for the C Language', 1981 by Bjarne
Stroustrup, '.' was used instead of '::'.) Scope resolution is a difficult
problem with regards to the inheritance of features from a common ancestor due
to multiple inheritance.  It would be more advantageous if such accesses were
provided by one consistent syntax.  (Scope resolution by the '::' operator is
rather like qualification by 'qua' in Simula.) It is better to make decisions of
this nature in declarations, and rely on the compiler to generate the different
code for different accesses, as if the declaration policy ever changes, then all
that is required is one recompilation, rather than any rework of the program
text by the programmer. 

In summary: access to members should be provided by the same syntax, as this
ensures implementation independence, and thereby enhances programming
flexibility.  A change in access policy may then be effected by the change in
the declaration only, and simple recompilation.  This is enabled by compiler
technology, which means the compiler can generate the correct code for the given
semantics, therefore relieving this burden from the programmer. 


On information, computation and execution and paradigms
-------------------------------------------------------

The role of programming may be seen as the separation of concerns.  This
separates information modeling from the specifics of computation, and thereby,
computation from the specifics of execution.  This model of programming ensures
data independence, and therefore flexibility and portability in software
production, and the above argument, and others in this paper are a consequence
of this.  The information model, can be seen as setting up policies of data
form, and type, and the interfaces and operations on this data.  The computation
model can be seen as specifics of the computations that can take place on the
information. 

The execution mechanism is a combination of compilers and the target
architecture and environment.  The execution mechanism is responsible for the
combination of policies from the computation domain, and the information domain,
into an executable system.  For example, if the computation 'a + b' is to be
executed by the execution mechanism, the execution mechanism must determine
whether a and b are integers or reals, and actually invoke the correct
mechanisms to execute the add based on those types.  Similarly, if 'a.b' is
specified in the computation domain, the execution mechanism must look up the
access policy on a, in the information domain.  If a is an object, then 'a.b' is
executed in C terms.  If a is a pointer to an object, then 'a->b' is executed in
C terms.  In a compiled system, these decisions are mode early, at compile time,
so there is no run-time overhead.  In an interpreted system, these decisions are
made at run-time resulting in some overhead, but with the advantage of extra
flexibility. 

A schematic of the information, computation and execution mechanism looks like:



        ----------------------          -----------------------
        |                    |          |                     |
        |      Information   |          |    Computation      |
        |         Model      |          |       Model         |
        |                    |          |                     |
        ----------------------          -----------------------
                     \                          /
        Information   \                        /  Computation
            Policies   \                      /  Policies
                        \                    /
                         V                  V
               -------------------------------------
               |                                   |
               |        Execution    Mechanism     |
               |  (Compiler + target architecture) |
               |                                   |
               -------------------------------------


The information and computation policies are determined by requirements
analysis, design, and implementation.  This separation of concerns, makes the
information, and computation aspects of a program independent, and this ensures
flexibility.  Interestingly, ISO's Reference Model for Open Distributed
Processing describes a similar model in order to ensure distribution
transparency of applications and services within a network. 

Another way to look at this is that the information model provides a schema, by
which the computational model is interpreted to be meaningful, and to which the
computational model must conform.  It is the compilers job to ensure that the
computational model conforms to the information model.  In natural language, we
use schemas frequently.  For example, the sentence "The boy drank the computer
and switched on the glass of water" is grammatically correct, but the
computational model of verbs does not conform to the information picture that we
understand about computers and glasses of water. 

Now programming has much more precisely defined semantics than natural language,
so naturally we should expect to find many more such schema checks within
programming languages. 

There is another concept in object-oriented programming which is closely
related, that of paradigm.  Object-orientation is a paradigm.  If the
object-oriented paradigm is strictly implemented, there are also many
consistency checks that a compiler may apply, similar to those of schema
checking.  Between the dual concepts of schema and paradigm, a solid and
disciplined framework for program development is provided. 

Multi-paradigmed approaches have also been proposed, to bring the advantages of
several paradigms into one framework.  But multi- paradigmed approaches have
their own problems, and haphazard mixing of paradigms can lead to
inconsistencies between the paradigms.  Indeed if two or more paradigms are
badly mixed, we may end up with something of little advantage.  At best
multi-paradigmed technology is immature.  To get any advantage of a multi-
paradigmed approach, paradigms will have to be carefully combined, and not
necessarily equally.  It has been suggested that C++ can be used for many
different styles or 'idioms' of programming.  This is very similar in concept to
multi-paradigmed programming, but it has been recognised that bending languages
to resemble other languages by defines and other techniques is problematic, and
the Swiss Army knife approach cannot be as efficacious as a tool which is
specifically designed for the purpose of application.  By this approach,
syntactic constructs similar to those used in the paradigm may be built, but in
general checks to make sure that the constructs are used consistently with the
paradigm are impossible. 

One of the strengths of the object-oriented paradigm is that it is excellent for
the large scale organisation of software.  Therefore, the object-oriented
approach may be most useful for providing the overall structure for systems
written in other paradigms.  The object-oriented paradigm may provide the
framework, within which other paradigms, such as logic, procedure, functional,
and machine oriented paradigms can exist.  Thus an object-oriented framework
will provide an object-oriented wrapping for embedding other paradigms.  One
example is Apple's MacApp, which provided an object-oriented wrapping around the
Macintosh Toolbox paradigm.  Machine and operating system paradigms may also be
wrapped in object-oriented interfaces, thereby centralising all environment
dependencies in one or as few places as possible. 


Anonymous parameters in Class Definitions
-----------------------------------------

In C++ parameters in function templates are not required to be named, but just
the type alone may be specified.  For example a function f in a class header can
be declared as f (int, int, char).  This gives no clue to a class user as to the
purpose of the parameter, without referring to the body of the class
implementation.  Meaningful identifiers are essential in this situation, as this
is the abstract definition of a routine, and a client of the class and routine
will need to know that the first int represents a 'count of apples', etc.  The
use of anonymous parameters handicaps the purpose of abstract descriptions of
classes and members: to facilitate the reusability of software.  Program text is
the medium by which the meaning of the system is captured for some future
activity, short or long term.  Communication of intent of a software element is
essential if reusability is to be achieved.  A compiler strips away this level
of communication, producing a machine executable entity.  The careful production
of a semantic entity should not be penalised by languages or compilers that
perform less than optimal translations.  But neither should a language
definition allow less than optimal expression to the human reader. 

The reason that anonymous parameters may be in C is that it is not necessary to
specify a name in a function template.  But then naming as has been pointed out
is not at all necessary in programming.  Naming only exists to help the human
reader identify different entities within the program, and to reason about their
function.  To this purpose naming is essential.  Anonymous parameters may save
typing in a function template, but then programming is not a matter of
convenience, especially, where such a convenience is not a matter of convenience
for later readers.  This is an example of where redundancy is beneficial, as it
saves a later programmer from having to go and look up the information in
another place.  A real convenience in function templates would be that abstract
function templates be automatically generated from the implementation text (see
header files for more details). 

The issue of anonymous parameters may be argued to be a safety issue, as a
programmer may guess at what the purpose of a parameter is from the type, but
guess wrong.  However, it is often counter argued that C programmers always know
what they are doing, and don't make this sort of mistake.  If we ignore then
that this could be a safety issue, it can still be labeled a 'courtesy' issue. 
Courtesy issues become even more important in the context of reusable software,
as an interface client must know what the intention of the interface is for it
to be used effectively.  A courtesy implies an inconvenience to the one who
provides the courtesy, but provides a convenience to one or more others. 
Courtesy issues include choosing meaningful identifiers, consistent layout and
typography, meaningful and non-redundant commentary, etc.  Courtesy issues are
not just a style consideration, but should be reflected in, and supported by
language design.  A language may support, but cannot enforce courtesy issues,
and it is often pointed out that poor, discourteous programs can be written in
any language.  But this is no reason for not being careful about the languages
that we develop and choose for software development. 

In summary: the purpose of programming is to capture the semantics of a system. 
As a matter of style, meaningful identifiers should always be used in function
templates in C++.  Reusable software requires that the semantics of the abstract
system are clear to enable that system to become a subcomponent of some other
system.  Courtesy issues are important if reusable software is to be realised,
and should be considered in the design of a language. 


Constructors
------------

Multiple constructors are allowed by having different arguments, as for
functions.  This has the disadvantage that it precludes having two different
constructors with the same arguments.  Such constructors may be useful for
initialising the object in different valid states.  Constructors are also not
named (apart from the same name as the class).  This makes it difficult to
discern from the class header the purpose behind the different constructors. 
Constructors suffer from all of the problems described with regards to functions
with the same name but different signatures.  It would be easy to mark routines
as constructors, for example:

	constructor make (...)...
	constructor clone (...)...
	constructor initialise (...)...

where each constructor leaves the object in valid, but potentially different
states. 

Declaring named entities is a redundant activity, but it was realised in the
transition from FORTRAN to ALGOL that such redundancy provides the mechanism
which enables compilers to detect certain classes of errors.  Naming is
essential in the management of state space of a program, so the C++
implementation of constructors is at best an example of dubious merit. 


Constructors and Temporaries
----------------------------

When can 'return <expression>' result in some value other than the result of the
expression being returned to the caller.  In section 6.6.3, the C++ ARM says "If
required the expression is converted, as in an initialisation, to the return
type of the function in which it appears.  This may involve the construction and
copy of a temporary object (S12.2)."

Section 12.2 explains "In some circumstances it may be necessary or convenient
for the compiler to generate a temporary object.  Such introduction of
temporaries is implementation dependent.  When a compiler introduces a temporary
object of a class that has a constructor it must ensure that a constructor is
called for the temporary object."

A note says "The implementation's use of temporaries can be observed, therefore,
through the side effects produced by constructors and destructors."

Now putting this together, creation of a temporary is implementation dependent,
so may or may not be done.  A constructor is called as a side effect, and this
can completely change the value held by the object if it wishes.  But as
different implementations may decide to do different things with temporaries,
this means that the potential exists that different results may result from
different C++ implementations. 
 

Optional Parameters
-------------------

Optional parameters which 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 may be
convenient in shell scripts, and interactive systems, but in large scale
software production, where precision is mandatory, can lead to ambiguities and
mistakes.  Firstly, optional parameters open up the possibility that the
programmer may assume the wrong default for a parameter.  Secondly, it is
possible that the programmer did not intend to omit the parameters (most errors
being, what the programmer did not intend), but optional parameters mean that
the compiler must assume that this is correct, and this bypasses the safety
check that parameters must match exactly those defined in the function header. 

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. 

This mechanism can be easily provided by other means, already in the language. 
For example, you can make a parameter optional by having a call to another
(inline) routine which provides the default.  This not only provides the
convenience of optional parameters, but is more powerful, as any parameter or
combination could be filled in with any combination of defaults, not just the
last parameters.  Multiple sets of defaults may be provided by multiple
intermediate routines. 

In summary: optional parameters provide little convenience, but open up the
possibility for errors.  Every convenience, and more, of optional parameters may
be provided by other language mechanisms.  In short, use these alternative
mechanisms in preference to optional parameters. 


Bad deletions
-------------

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

	p = new int[10];
	p++;
	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, which are ill-defined language
constructs, which can only be covered by warnings of potential disaster.  To
avoid such language deficiencies would result in loss of compatibility with C,
but this may be a good thing if problems such as the above disappear.  But then
the resultant language may be as far removed from C as to suggest that C is
abandoned altogether. 


Local entity declaration
------------------------

The ability to declare an entity close to where it is used, has both advantages
and disadvantages.  It is a convenience, as you can declare the entity close to
where it is used, but this can make a routine appear more complex and cluttered. 
Another potential problem is that an identifier can be mistakenly overloaded
within a nested block in a function, with the resultant problems covered in name
overloading and nesting.  This simple form of name overloading was used in ALGOL
(name overloading has been around a long time, in fact the designers of Simula
realised this, and hence object-orientation was born, although they thought it
was just structured programming if you read the book by Djikstra, Hoare and
Dahl).  As has already been pointed out in the section on name overloading,
overloading is best provided in limited by well controlled forms.  C originally
avoided this problem by not having nested routines or blocks, (a block in the
ALGOL sense contains both declarations and instructions.)

It should also be of concern that the local entity declaration scheme of C++
does not ensure the 'separation of concerns', as described in the section of
information, computation, and execution mechanisms, giving the benefits of data
independence, and implementation transparency.  This is important in large scale
software development, and protection of programming investment, should the
requirements of the system be changed. 

The ARM should be consulted for further problems of local declarations vs
branching, which shows the complications in intermingling declarations and
instructions.  Again fixing poor language definition by caveats cannot make up
for the faulty definition. 

The C++ FAQ (Q83) is not very clear 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 subject of a 'new' instruction.  In
well written object-oriented software, routines will be small, typically
performing one atomic action per routine*, and therefore unneeded entities will
not be constructed until the routine is actually called.  This strategy means
the provision of such locals is less likely to be of any benefit.  If as the FAQ
suggests, that initialising an object is so expensive, then the overhead of a
routine call will be negligible, thereby realising the efficiency gain by good
modularisation by subroutines. 

Further if an object is in the stack, this suggests that its constructor should
be simple, only initialising the object to some neutral state (like integers to
0, booleans to false, etc), leaving the bulk of the work to where the object is
actually used.  Thus a complex constructor can be broken up into a simple part,
which just gets the object going, and a more complex part in a normal routine,
which is called closer to the time of use.  On the whole, this seems to be a
better modularised approach.  In fact allocating all locals at the beginning of
a routine may be an optimisation on some processors (like the M68000), where the
stack space for all locals is allocated in one instruction by adjusting the
stack top, rather than many little increments for each individual declaration
statement. 

Other than that, the FAQ offers some excellent general advice on optimisation in
this section, as you should not embark on expensive creation operations before
doing some cheap checks to see whether that is necessary.  Also the criticism in
the FAQ of 'set' methods is quite useful. 

*Small routines which implement atomic operations are fundamental to loose
coupling.  For example, a base class that provides a routine that logically
performs operations A and B, is not useful to a subclass which wants to perform
A as it was in the base class, but needs to provide its own implementation of
the logic of B.  This way the descendant is forced to reimplement the logic of
both A and B, missing an opportunity to reuse the logic labeled A.  Such tight
coupling is to be avoided, as it reduces flexibility. 

In summary: due to the typically small routines written in object-oriented
systems, declarations intermingled with instructions result in very little
convenience, and if the system is designed correctly, will probably not be used
a great deal.  Local declarations lead to complications, and also can result in
unintended name overloading, due to nesting.  Use of local declarations means
that information and computation concerns are not cleanly separated. 


Friends
-------

Friends provide a mechanism for overriding data hiding.  A friend class or
function of a class has access to the private data of a class.  Friend is a
'limited export' mechanism.  The friend function or class has access to all the
members in the private section of a class.  There are three problems with
friends:

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

The usefulness of friends shows that there is a case for shades of grey between
public, protected and private members.  The functionality of friends may be
provided in ways that do not have the above three problems, by ways which are
consistent with the object-oriented paradigm, while not requiring the
introduction of a foreign concept such as friend.  The programmer may make
careful use of such mechanisms by careful class design, which leads to as little
coupling between components as possible, in conjunction with a selective export
mechanism.  A selective export mechanism (more general than public, private,
protected and friend), will also document the couplings between entities that
can occur in the system, as does friend.  Selective export specifies that not
only should a member be exported but to which other classes specifically to
which it is to be exported. 

One reason given for the use of friends, is that it allows efficient access to
data members, which would otherwise have to be accessed via a function call. 
The way C++ is often used is that data members should not be put in the public
section, as this breaks the data hiding principle. 

Data hiding may be better described as 'implementation hiding'.  The interface
to the external world, exports entities that return values of given types, so
there should be no restriction on the export of variable members.  The strategy
is that entities should not be exported if they expose any detail of
implementation, and that exported variables are treated by the outside world in
the same manner as functions, ie they cannot be assigned to.  This is because,
when used in expressions, there is no semantic difference between functions and
variables, as 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. 

In summary" The concept of friend may be provided more generally and
consistently by selective export, which specified which entities are to be
exported, and to which classes. 


Static
------

Static is a word that causes much confusion in C++.  This confusion is mentioned
on p.  98 of the C++ Annotated Reference Manual (ARM), which claims it has two
meanings.  Actually it has three meanings.  Firstly, as above, a class may have
static members, and a function may have static entities.  The second meaning
comes from C, where an entity declared static is local in scope to the current
file.  The choice of independent keywords would easily solve this trivial
problem.  The third meaning is a general object-oriented meaning, not specific
to C++.  It is that objects may be statically allocated, as opposed to
dynamically allocated in free space, or automatically allocated and deallocated
on the stack, when a block is entered. 

Static class members are a useful concept, but of limited use, and the
functionality can easily be provided without the extra notion of static.  On
page 181 of the ARM, the reason is given that statics will reduce the need for
global variables.  Such members though may be placed in a class of their own,
and one copy only of this class is instantiated (a global object).  Each object
is instantiated with a reference to the object with the static data.  For
example

class static_data
{
	// Only one object of this class is instantiated.

	... static member declarations
}

class original_class
{
	static_data * our_statics;
}

This is a clue as to why global entities are unnecessary in object-oriented
programming.  (See the note on name overloading and nesting, and global
environments.)

Static may also be applied to declarations in functions.  These are not needed
in an object-oriented language.  The reason and history is this.  ALGOL had the
notion of 'OWN' locals in blocks.  The semantics of an OWN entity was that on
every entry to the block, it preserved the value from the last entry to the
block.  The implementation of this was that at run time, it was kept in the
global environment, but at compile time, the scope of the OWN entity was limited
to within the block in which it was declared.  Now this caused complication in
recursion, as the same instance of the variable was used in all invocations of
the procedure, rather than each invocation using separate local storage on the
stack. 

The designers of Simula generalised the ALGOL notion of block, so that instead
of deallocating the storage on block exit, that storage could be made
'persistent', and so object-oriented classes were born.  Now since the
declarations in this 'class' block were no longer deallocated, this removed the
need for the OWN concept, which had proven problematic in ALGOL, so there is no
OWN in Simula.  So there is no reason for static or OWN declarations in
object-oriented languages, as this is achieved by making the static or OWN a
class member, if persistency is required in the lifetime of the object, or if
this value is required to be shared among objects, then it may be provided by a
shared object, as suggested for static class members.  If persistency is
required for the lifetime of the program, then this shared object will not be
deleted until the program is terminated. 

In summary: the static keyword and concept has been confused by overloading one
word with different semantics.  The object-oriented concept of class is very
simple, yet very powerful.  It is the basis for a consistent division of a
programs state space.  The power of this method is that other mechanisms for
storing permanent data in a running program are not needed, and only complicate
systems.  The mechanisms that are not needed are globals, static members, and
static declarations (OWNs). 


Union
-----

As with static, this is another construct not needed in OOP.  Firstly, similar
concepts have been recognised as problematic in other languages, for example,
FORTRAN's equivalences, COBOLs REDEFINES, and Pascal's variant records.  Such
mechanisms when used for overloading the same memory space forced the programmer
to think about how achieve this.  The stack mechanism in recursive languages
meant that this was not necessary, as memory was automatically overloaded for
the locals of procedures, and the same space was reused as procedures were
entered and exited.  So this use of union when used similarly to FORTRAN's
equivalences is not needed. 

Union is also not needed to provide the equivalent to COBOL REDEFINES or
Pascal's variants.  In OOP though, this is provided for by inheritance.  A
reference declared as a superclass may be used to refer to all subclasses, and
this way provides the same semantics as union, only in a type safe manner.  A
declaration of a reference to a class is implicitly a union of all subtypes of
the class. 


Nested Classes
--------------

Nested classes were provided in Simula, in much the same way that ALGOL provided
nested procedures.  This was later realised to be of questionable value, as it
was not in the free spirit of object-oriented decomposition, where classes
should be loosely coupled, thereby supporting software reusability.  C indeed
did not have the complexity of nested functions, but C++ has chosen to implement
this complexity for classes, which is of less use than nested functions. 
Nesting is achieved more generally by inheritance.  In C++, not only may classes
be nested within other classes, but they may also be defined within functions,
thereby tightly coupling a class to a function.  The concept of object-oriented
programming is that the class is the fundamental structure, and that nothing has
existence separate from class (including globals), but C++ is confused as to
whether it is procedure-oriented or object-oriented.  While the ability to able
to have classes local to functions may not cause any practical problems aside
from precluding the class definition from being reusable, it causes theoretical
and conceptual problems, which should not be ignored as nested classes are of
little practical use.  Of course examples may be given, but such examples may
also be structured in simpler, more straightforward ways, without loss of
generality.  (See also the general comments on name overloading and nesting.)

Classes that can be nested within other classes and functions also raise
questions about the usefulness of this approach to multi-paradigmed programming. 
It seems that the object-oriented approach will be most useful if it is used for
the overall organisation of software, encapsulating other paradigms if
necessary.  However, C++ has combined paradigms in such a way, that object-
oriented encapsulation, and the advantages that it realises are no longer
guaranteed.  Of course it may be pointed out that there are examples that may be
written using the C++ approach, but maybe such examples may be better left to
academia and research, rather than trade off consistency, which will be most
beneficial for software production houses, which require commercial grade tools,
rather than a general purpose research harness. 


Global Environments
-------------------

C++ functions may change the global environment, beyond the object in which they
are encapsulated.  Such changes are side-effects that limit the opportunity to
produce loosely-coupled objects, which is essential to the production of
reusable software, as it is difficult to remove the class from its surrounding
environment.  This is a drawback of both global and nested environments.  In
fact the issue of side-effects is the most consistently addressed issue in the
OO world. 

A good OO language will permit changes to an objects state to be made only by
routines in that object.  Removal of a global environment is a technically
trivial exercise.  It is simply the requirement that the global environment be
encapsulated in an object or set of objects of its own, after all that is what
objects do: provide an encapsulated environment, and there is no reason why the
global or external environment should not be encapsulated.  Such objects
facilitate a clean object-oriented interface to the environment external to the
system, without loss of generality, for a negligible performance penalty.  Thus
classes are independent of a surrounding environment, and the project for which
they were first developed, and are therefore easily adaptable to new
environments and projects.  Software written in C++ has the Unix style
environment interweaved throughout programs.  This is not the way to open
systems and cross platform development. 

(See also the note on name overloading and nesting.)

In summary: The object-oriented concept of class is very powerful, and changes
the way software should be structured quite radically.  Globals are problematic
in that they provide a mechanism whereby everything has free access to them,
rather than the disciplined access of class.  Since globals are just an
environment, it is simple to encapsulate this environment into an object or
objects of its own. 


Header Files
------------

In C++ a class interface must be maintained separately from its body.  While an
abstract interface should be distinct from a concrete implementation, the
interface and implementation may be both derived from one source.  In C++
though, programmers must maintain the two sets of information.  The drawbacks of
replicated information are well understood.  In the event of change, both copies
must be updated, which could lead to inconsistencies, which must be detected and
manually corrected.  It is possible to provide tools that will automatically
extract the abstract class header definitions from class implementations, and
guarantee consistency. 

Also, the programmer must consciously make those headers of imported classes
available.  If the correct #includes are not specified, this results in a round
of syntax errors.  Another problem is that if header A includes header B, and
header B includes header A then a circular dependency is set up.  The same
problem also occurs if header A includes headers B and C, and header B also
includes header C.  This is overcome by the simple but messy fix in all headers
of

#ifndef thismod
#define thismod

... rest of header
#endif

Headers show how C++ addresses the problem of independent modules by a
non-object-oriented approach which is sub-optimal as it requires the programmer
to supply this information manually.  A class interface is the OOP equivalent of
a module header.  A class definition contains all knowledge of component classes
and their dependencies (inheritance and client) in the class text.  Dependency
analysis is derivable from the program text, and functionality of tools like
'make' can be integrated into the compiler itself, and the errors encountered in
the use of 'make' are completely avoided. 

The mechanism for getting the header file information into a module is #include. 
#include is an old and not very sophisticated mechanism for the implementation
of modularity.  C++ still uses this 30 year old technique for modularisation,
while other languages have adopted more sophisticated approaches, for example,
Pascal with Units, Modula with modules, Ada with packages, and in Eiffel the
unit of modularisation is the class itself, and all includes are handled
entirely automatically.  An OOP class is the more modern and sophisticated way
of modularising programs, so there should be no need to support the #include
mechanism in C++, at least for class dependency analysis, although required in
order to remain compatible with C.  Multiple inheritance is a form of #include. 

To clarify the relationship between modules and classes further, a module
contains data and routines that manipulate that data.  A system is assembled by
combining modules.  A class also contains data, and routines that act on that
data.  An object-oriented system is assembled by the combination of classes. 
Modules are a primitive form of classes.  Classes are more sophisticated, as
they express with much more precision the relationships with other classes.  C++
#includes and modules have problems, and this primitive building block method is
not required in an object-oriented language. 

In summary: In an object-oriented program, classes form the basis of modularity. 
The #include mechanism is old, and must be explicitly stated by the programmer,
and is not necessary given all dependency information is implicitly derivable
from the text of an object-oriented program.  Multiple inheritance is a form of
#include.  The class interface is the object-oriented equivalent of the module
header, so headers and #include mechanisms seem out of place in an
object-oriented language. 


Class header declarations
-------------------------

C's syntax for function declarations is [<type>] <identifier> (<parameters>). 
For (a very simple) example:

class C
{
	a ();
	b ();
	int c ();
	d ();
	char e ();
	virtual void f ();
}

To find an identifier in this layout, the eye must trace a course around the
type specifications.  This leads to a tiring activity, and a greater chance of
the eye missing the sought identifier, so that the programmer must resort to
using the search function of a text editor to help out. 

Other languages have chosen a different approach, placing the entities name
first.  For example:

class C
{
	a ();
	b ();
	c () int;
	d ();
	e () char;
	f () virtual void;
}

This seems backwards at first to those used to the ALGOL and FORTRAN styles of
type first.  But there is a very good simple reason for it.  An example from the
real world may illustrate the argument for name first.  Imagine if a dictionary
was published, and the keywords were not placed first, but rather the entry
order was -

noun /obvrzen/ obversion, the act or result of obverting

the dictionary would not sell many copies, unless the marketers managed to
convince large numbers of the population that there was something intrinsically
magical about this order of layout, that made the explanation of the meaning
more correct.  This example illustrates how subtle syntax decisions can be very
important, and why the PASCAL style of languages may have ordered things this
way around, contrary to FORTRAN, ALGOL and others.  These are trivial but
important alternatives that the language designer must consider. 

In summary: layout of programming entities is essential for effective
communication.  Layout is provided by the dual roles of language syntax, and
programming style.  A dictionary or index style layout would suggest placing
entity names first, followed by their definition. 


-- 
Ian Joyner                                     ACSNet: ian@syacus.acus.oz
ACUS (Australian Centre for Unisys Software)   Internet: ian@syacus.acus.oz.au
Tel 61-2-390 1328      Fax 61-2-390 1391       UUCP: ...uunet!munnari!syacus.oz
Newsgroups: comp.lang.c++