| Smalltalk | Eiffel | C++ | Java |
uniformity | high | medium | low | medium |
documentation value | medium | high | medium | high |
reliability | medium | medium | low* | high* |
protected operations | no | no | yes | yes |
multiple inheritance | no | yes | yes | no* |
efficiency | low | medium | high | low |
garbage collection | yes | yes | no* | yes |
language complexity | low* | medium | high | medium |
slide: Comparing Smalltalk, Eiffel, C++ and Java
This characterization conforms to the one given in
[BPS89], with which I think the majority of the
object-oriented community will agree.
It is further motivated below.
However, the places indicated by an asterisk
deserve some discussion.
In particular, I wish to stress that I disagree with
characterizing the reliability of C++ as low.
(See below.)
Uniformity
In Smalltalk, each data type is described by a class.
This includes booleans, integers, real numbers and control
constructs.
In Eiffel there is a distinction between elementary
data types (such as boolean, integer and real)
and (user-defined) classes.
However (in the later versions of Eiffel)
the built-in elementary types behave as if declared
by pre-declared classes.
For C++, the elementary data types and simple data structures
(as may be defined in C) do not behave as objects.
To a certain extent, however,
programmers may deal with this non-uniformity by some
work-around, for example by overloading functions
and operators or by embedding built-in types in a (wrapper) class.
Java may be regarded as a simplified version of C++.
Due to its restrictions, such as the absence of
operator overloading and type casts,
the language appears to be more uniform
for the programmers.
Documentation value
Smalltalk promotes a consistent style in writing programs,
due to the assumption that everything is an object.
One of perhaps the most important features
of Eiffel is the use of special keywords for
constructs to specify the correctness of programs
and the behavioral properties that determine the external
interface of objects.
Moreover, Eiffel provides a tool to extract a description
of the interface of the method classes
(including pre- and post-conditions associated with a method)
which may be used to document (a library of) classes.
To my taste, however, the Eiffel syntax leads to
somewhat verbose programs, at least in comparison
with programs written in C++.
The issue of producing documentation from C++ is still open.
A number of tools exist (including a WEB-like system
for C++ and a tool to produce manual pages from C++
header files) but no standard has
yet emerged.
Moreover, some people truly dislike the terseness of C/C++.
Personally, I prefer the C/C++ syntax above the syntactical
conventions of both Eiffel and Smalltalk,
provided that it is used in a disciplined fashion.
Java programs may be documented using javadoc.
The javadoc program may be regarded as the standard
C++ has been waiting for, in vain.
Reliability
Smalltalk is a dynamically typed
language.
In other words, type checking, other than detecting runtime errors,
is completely absent.
Eiffel is generally regarded as a language possessing
all characteristics needed for writing reliable programs,
such as static type checking and constructs for stating
correctness assertions (which may be checked at runtime).
Due to its heritage from C, the
language C++ is still considered by many as unreliable.
In contrast to C, however, C++ does
provide full static type checking, including the signature
of functions and external object interfaces
as arise in independent compilation of module files.
Nevertheless, C++ only weakly supports type checking
across module boundaries.
Contrary to common
belief, Eiffel's type system is demonstrably inconsistent,
due to a feature that enables a user to dynamically define the
type of a newly created object in a virtual way
(see section [self-reference]).
This does not necessarily lead to type-insecure
programs though, since the Eiffel compiler
employs a special algorithm to detect such cases.
The type system of C++, on the other hand, is consistent
and conforms to the notion of subtype as introduced
informally in the previous part.
Nevertheless, C++ allows
the programmer to escape the rigor of the
type system by employing casts.
An important feature of Eiffel
is that it supports assertions that may be validated
at runtime.
In combinations with exceptions, this provides
a powerful feature for the development
of reliable programs.
At the price of some additional coding (for example,
to save the current state to enable the use
of the old value), such
assertions may be expressed by using the assert macros
provided for C++.
In defense of C++,
it is important to acknowledge that C++ offers
adequate protection mechanisms to shield classes
derived by inheritance from the implementation details
of their ancestor classes.
Neither Smalltalk nor Eiffel offer such protection.
Java was introduced as a more reliable variant of C++.
Java's reliability comes partly from the shielded
environment offered by the Java virtual machine,
and partly from the absence of pointers and
the availability of built-in garbage collection.
Practical experience shows that for the average student/programmer
Java is indeed substantially less error-prone than C++.
Inheritance
Smalltalk offers only single inheritance.
In contrast, both Eiffel and C++ offer multiple inheritance.
For statically typed languages, compile-time
optimizations may be applied that result in only
a low overhead.
In principle, multiple inheritance allows one to model
particular aspects of the application domain in a flexible
and natural way.
As far as the assertion mechanism offered by Eiffel
is concerned,
[Meyer88] gives clear guidelines prescribing how
to use assertions in derived classes.
However, the Eiffel compiler offers no assistance
in verifying whether these rules are followed.
The same guidelines apply to the use of assertions
in C++, naturally lacking compiler support as well.
The Java language offers only single (code) inheritance,
but allows for multiple interface inheritance.
The realization of (multiple) interfaces seems to be
a fairly good substitute for multiple (implementation) inheritance.
Efficiency
Smalltalk, being an interpreted language, is typically
slower than conventionally compiled languages.
Nevertheless, as discussed in section
[self-implementation], interpreted object-based
languages allow for significant optimizations,
for example by employing runtime compilation techniques.
The compilation of Eiffel programs can result in programs
having adequate execution speed.
However, in Eiffel dynamic binding takes place in principle
for all methods.
Yet a clever compiler can significantly
reduce the number of indirections needed to execute a method.
In contrast to C++, in Eiffel all objects are created
on the heap.
The garbage collection needed to remove these objects
may affect the execution speed of programs.
C++ has been designed with efficiency in mind.
For instance, the availability of inline functions,
and the possibility to allocate objects on the runtime stack
(instead of on the heap),
and the possibility to declare friend functions
and classes that have direct access to the private
instance variables of a class
allow the programmer to squeeze out the last drop
of efficiency.
However, as a drawback, when higher level functionality
is needed
(as in automatic garbage collection)
it must be explicitly programmed, and a similar price
as when the functionality would have
been provided by the system has to be paid.
The only difference is that the programmer has a choice.
At the time of writing there does not exist a truly
efficient implementation of the Java language.
Significant improvements may be expected from
the JIT (Just In Time) compilers that produce native code
dynamically, employing techniques as originally developed for
the Self language, discussed in section [Self].
Language complexity
Smalltalk may be regarded as having a low language complexity.
Control is primarily effected by message passing,
yet, many of the familiar conditional and iterative
control constructs reappear in Smalltalk programs
emulated by sending messages.
This certainly has some elegance, but does not necessarily
lead to easily comprehensible programs.
Eiffel contains few language elements
that extend beyond object-oriented programming.
In particular, Eiffel does not allow for overloading
method names (according to signature) within a class.
This may lead to unnecessarily elaborate method names.
(The new version of Eiffel (Eiffel-3) does
allow for overloading method names.)
Without doubt, C++ is generally regarded as a highly complex
language.
In particular, the rules governing the overloading of operators
and functions are quite complicated.
The confusion even extends to the various
compiler suppliers, which is one of the
reasons why C++ is still barely portable.
Somewhat unfortunately,
the rules for overloading and type conversion
for C++ have to a large extent been determined
by the need to remain compatible with C.
Even experienced programmers need occasionally
to experiment to find out what will happen.
According to [BPS89], C++ is too large and contains
too much of the syntax and semantics inherited from C.
However, the validity of their motto small is beautiful
is not as obvious as it seems.
The motivations underlying the introduction of
the various features incorporated in C++
are quite well explained in [Stroustrup97].
The main problem, to my mind, in using
C++ (or any of the object-oriented languages for
that matter)
lies in the area of design.
We still have insufficient experience in using abstract
data types to define a complete method and operator interface
including its relation to other data types (that is its behavior
under the various operators and type conversions
that apply to a particular type).
The problem is hence not only one of language design but
of the design of abstract data types.
Java is certainly less complex than C++.
For example, it offers no templates, no operator overloading
and no type coercion operators.
However, although Java is apparently easier to use, it is
far less elegant than C++ when it comes to creating user-defined types.
Class interfaces in Java are usually much more verbose
than similar interfaces in C++.
And, due to the absence of templates,
type casts are necessary in many places.
On the other hand, casts in Java are type safe.