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


it is located in the global stack
frame.  The same instance of the variable is used in all invocations of
the procedure, rather than each invocation using separate local storage
on the stack.  This causes complication in recursion.

   Simula's designers generalised the ALGOL notion of block into class,
and so object-orientation was born.  Instead of discarding a class block
on exit, it is made 'persistent'.  Declarations within the class block
are persistent, and therefore provide the functionality of static and
OWN.  Classes are more flexible than statics.  Statics are persistent in
the same way as globals, ie for the duration of the program.  Class
member lifetime is governed by the lifetime of the object.
Object-oriented languages do not need OWNs or statics.

  

3.18.  Union

   Union is another construct that is superfluous in OOP.  Similar
constructs in other languages are recognised as problematic.  For
example, FORTRAN's equivalences, COBOLs REDEFINES, and Pascal's variant
records.  When used to overload memory space these force the programmer
to think about memory allocation.  Recursive languages use a stack
mechanism that makes overloading memory space unnecessary, as it is
allocated and deallocated automatically for locals when procedures are
entered and exited.  The compiler and run time system automatically
allocate and deallocate storage as required, ensuring that two pieces of
data never clash for the same memory space.  This is essential so that
the programmer can concentrate on the problem domain, rather than
machine oriented details.  When union is used similarly to FORTRAN's
equivalences it is not needed.

   Union is also not needed to provide the equivalent to COBOL REDEFINES
or Pascal's variants.  Inheritance and polymorphism provide this in OOP.
A reference to a superclass can also be used to refer to any subclass,
and thus provides the same semantics as union, only in a type safe
manner, as the alternatives can never be confused.  An object reference
is implicitly a union of all subclasses.

  

3.19.  Nested Classes

   Simula provided textually nested classes similar to nested procedures
in ALGOL.  Textual (syntactic) nesting should not be confused with
semantic nesting, nor static modelling with dynamic run time nesting.
Modelling is done in the semantic domain, and should be divorced from
syntax.  You do not need textually nested classes to have nested
objects.  Nested classes are contrary to good object- oriented design,
and the free spirit of object-oriented decomposition, where classes
should be loosely coupled, to support software reusability.  Semantic
nesting is achieved independently of textual nesting.  In
object-oriented design all objects should interact only via well defined
interfaces.  Objects of a class that is textually nested in another
class have access to the outer object without the benefit of a clean
interface.  C avoided the complexity of nested functions, but C++ has
chosen to implement this complexity for classes, which is of less use
than nested functions.

   OOP achieves nesting in two ways: by inheritance and object-oriented
composition.  Thus modelling nesting is achieved without tight textual
coupling.  For example, consider a car.  We know in the real world that
the engine is embedded within the car.  In object-oriented modelling,
however, this embedding is modelled without textual nesting.  Both car
and engine are separate classes.  The car contains a reference to an
engine object.  This also allows the vehicle and engine hierarchy to be
independently defined.  Engine is derived independently into petrol,
diesel, and electric engines.  This is simpler and more flexible than
having to define a petrol engine car, a diesel engine car, etc, which
you have to do if you textually nest the engine class in the car.  Other
examples can also be structured without textual nesting, and no loss of
generality.

   In C++, not only can classes be nested within other classes, but also
within functions, thereby tightly coupling a class to a function.  This
confuses class definition with object declaration.  The class is the
fundamental structure in object-oriented programming and nothing has
existence separate from class (including globals).  C++ is confused as
to whether it is procedure-oriented or object- oriented.

  

3.20.  Global Environments

   The global environment provides a special case of nested classes.
When classes are nested in a global environment, dependencies can arise
that make the classes difficult to decouple from that environment, and
therefore not reusable.  Even if a class is not intended for use in
another context, it will benefit from the discipline of object-oriented
design.  Each class is designed independently of the surrounding
environment, and relationships and dependencies between classes are
explicitly stated.

   In C++ functions can 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 enable reusable software.  This is a drawback of both
global and nested environments.

   A good OO language will only permit routines in an object to change
its state.  Removing the global environment is trivial.  It is simply
encapsulated in an object or set of objects of its own.  Therefore
global entities are subject to the discipline of object- oriented
design.  Having globals in a system circumvents OOD.  Objects can also
provide a clean interface to the external environment, or operating
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 more easily
adaptable to new environments and projects.

  

3.21.  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 can both be derived
from one source.  In C++ though, programmers must maintain the two sets
of information.  Replicated information has well known drawbacks.  In
the event of change, both copies must be updated.  This can lead to
inconsistencies that must be detected and corrected.  Tools can
automatically extract abstract class descriptions from class
implementations, and guarantee consistency.

   The programmer must also use #includes to manually import class
headers.  #include is an old and unsophisticated mechanism to provide
modularity.  #include is a weak form of inheritance and import.  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.  In Eiffel
the unit of modularisation is the class itself, and includes are handled
automatically.  The OOP class is a more sophisticated way to modularise
programs.  Inheritance implements reusability and modularisation, so
#include is superfluous.

   Another problem is that if header A includes header B, and header B
includes header A a circular dependency occurs.  The same problem occurs
if header A includes headers B and C, and header B also includes header
C.  A simple but messy fix in all headers solves this problem:

   #ifndef thismod
   #define thismod

   ... rest of header
   #endif


   Headers show how C++ addresses the problem of independent modules by
a non-object-oriented approach that is sub-optimal; the programmer must
supply this bookkeeping information manually.  A class interface is
equivalent to a module header.  A module header contains data and
routines exported to other modules.  This is exactly the purpose of the
class interface.  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 class text.  Tools like
'make' can be integrated into the compiler itself, and the errors and
tedium encountered in the use of 'make' are avoided.  #includes relate
to the organisation and administration of a project.  Rational language
design eliminates such bookkeeping mechanisms.

   A traditional system is assembled by combining modules.  An
object-oriented system is assembled by combining classes.  Modules are a
primitive form of classes.  Classes are more sophisticated.  They
express more precisely relationships with other classes.  C++ #includes
and modules have problems.  This primitive method is not required in an
object-oriented language.

  

3.22.  Class Interfaces

   Section 9.1c of the C++ ARM points out that C++ has no direct support
for "interface definition" and "implementation module".  In a C++ class
definition, all private and protected members must be included in the
public text of the class.  The ARM points out that whenever the private
or protected parts are changed, the whole program must be recompiled.
Further to what the ARM says, all modules that are dependent on the
header file must be recompiled, even though the private and protected
members do not affect other modules.  Private members should not be in
the abstract class interface, as this exposes implementation details to
programmers of other modules.

  

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

Page : << Previous 8  Next >>