Editor's note: This article was written more than 10 years ago and is comparing C++ as it was available then to Ada 95. Both languages have evolved since then and some details have changed. We have not attempted to update the article to reflect such details.
The Advantages of Ada 95
Ada 95 and C++ are modern general-purpose languages of roughly similar power, aimed broadly at the area of systems programming. Ada 95, like Ada 83, is also intended for embedded and real-time systems, and has a number of built-in features to support concurrent and distributed programming.

Both Ada 95 and C++ have features that modern software engineering practice considers indispensable: modularity, information hiding, structuring tools for large programs, inheritance and support of object-oriented design methods, various mechanisms for parametrizing software components, etc. Both languages are clearly superior to their competitors and predecessors (C, Modula-2 Pascal, Eiffel) in terms of expressive power, maturity, and software base. And Ada is also superior to these in terms of safety and reliability.

Ada 95, though, has several advantages over C++, in particular when software costs are examined over the lifetime of the software system. Rather than being comprehensive, this article focuses on details where the philosophies of both languages are in sharpest contrast. 'Ada' will be used to refer to the newest version of Ada, Ada 95.

Anything C++ Can Do, Ada Can Do as Well

The comparison of programming languages is, in part, a subjective affair: judgments are influenced by personal stylistic choices, familiarity, the 'first-language effect,' etc. Given that all programming languages in use are Turing-equivalent, no claim can be made as to the absolute advantage of one over the other. Rather, the arguments have to be pragmatic ones: reliability, ease of use, and modifiability of the resulting software. Social considerations are also relevant, such as the programming culture, training of programmers, availability of tools, mechanisms by which programming idioms circulate through the community, etc.

For example, consider the question of library management, a vital issue in every sizable project. The integrity of an Ada system is protected first by the library mechanism, whose definition is an integral part of the language. Every large project will build on top of the library management some configuration control machinery that simplifies the coordination of the work of many programmers, but the first guarantee of reliability is that the Ada system itself will not let you build a system with out-of-date components.

The C++ programmer (or the C programmer for that matter) will claim that he obtains the same effect by the use of the Make utility and by the disciplined naming conventions of the header files. The assertion is true, but to replicate the mechanisms that Ada readily supplies, it takes more than just the C++ language--it requires a number of utilities and tools that are common in the UNIX environment, but are in no way standardized and are not part of the language. Thus, there are no semantic checks on their use, and no relationship between these tools and the language translator itself. C and C++ depend critically on their environment. This dependence has three consequences:

  1. When judging the size of the language (as is heard in the frequent complaint: 'Ada is too big'), one must consider that the competent use of C++ requires much more knowledge than just that of the language; it requires comparable familiarity with a number of tools in the environment. The aggregate baggage, then, is larger for C++ than for Ada (given that the size of C++ alone is already as large as Ada 95, and at least as complex).
  2. Second, even if implementations of the language are standardized (which is far from true for C++), differences in the environment and its tools make portability harder to achieve. Programs can easily become contaminated with environmental details. To the C++ programmer who says, 'this is no problem, because if you just follow coding conventions the problems disappear,' Ada programmers can only reply that these coding conventions are additional baggage that the programmer must know, that they add to the cost of using the language, and that they are inherently less safe than comparable features that are built into the language.
  3. Finally, the programmer's knowledge is less portable and therefore less valuable; a solution adopted in one shop will be foreign and useless to another.
It may be concluded, then, that the C++ culture requires a larger aggregate knowledge than the Ada culture, that this knowledge is ill-defined at the interface between language and environment, and that it results in lessened portability and, in turn, greater maintenance costs than comparable Ada software.

The Type Model: What is an Array?

In spite of the fact that arrays are one of the oldest composite types in programming languages, there are sharp differences between their realization in C++ and Ada. C++ follows the same approach as the C language: arrays are closely related to pointers, and the indexing operation is described directly in terms of pointer arithmetic. This approach has several consequences:
  1. The indexing operation is not automatically checked. The language reference indicates that out-of-bounds indices are illegal, except that they are allowed to point one element past the end of the array (for reasons having to do with common programming idioms for iterating over an array); but there are not automatic bound checks at run-time. Given that indices out-of-bounds are the most common type of run-time error, this makes the use of arrays in C++ inherently less safe than in Ada.
  2. There is no array assignment: if an array is a pointer, then an assignment to the name of the array is a pointer assignment that aliases the array, not a data transfer operation.
  3. Multidimensional arrays are defined as arrays of arrays; multidimensional arrays must be declared with constant bounds (after the first) and have no built-in descriptors, which Ada requires to support array attributes. Linear algebra packages, for example, must carry explicit size parameters, or else the user is forced to play aliasing tricks between one- and multi-dimensional arrays, as is done in FORTRAN. Thus, the basic data type of scientific programming is described in low-level storage terms. In contrast, multidimensional arrays in Ada are described in terms of formal operations of an abstract type. In this area, C++ is clearly a lower-level language.
  4. All array types have the same index type. Programmers cannot define an array over an enumerated type, and there is never any type checking on indices.
The first consequence in particular is well-known and commonly acknowledged by the C++ community. Stroustrup, in his book The C++ Programming Language, correctly describes arrays as low-level structures and presents a user-defined vector class (on which indexing is checked) as a better abstract type. Unfortunately, the proper implementation of the class requires exceptions, a feature which has only recently been introduced into C++ and which is not yet widely or consistently implemented. In the meantime, programming without index checking is much like driving without seat belts based on the optimistic conviction that most drivers are sober. This optimism pervades the C and C++ communities. Ada programmers, on the other hand, are more wary of their surroundings.

It should also be noted that compilers make a big effort to optimize the use of built-in arrays, because they are the basic structure of many compute-intensive programs. Compilers are much less likely to treat user-defined arrays with the same care (there are no anticipated versions of LINPACK that use C++ vector classes).

It may be concluded here that C++ software is less reliable than Ada code and that for scientific programming, C++ is a lower-level language than Ada.

Classes, Inheritance, and Objects

Object-oriented programming (OOP) and design has become the buzzword of the decade. Behind the obvious enthusiasm of the software community, there lies the fact that OOP simplifies the design and construction of software systems. The most basic way it simplifies design and construction is through ease of reuse.

The notion of an object is vague and has been used to mean variously abstract type active agent, message receiver, etc. (A good case can be made that Ada tasks are the perfect model for objects and that Ada was one of the earliest object-oriented languages.) In fact, the usefulness of OOP does not lie in this fuzzy notion of object. Rather, it lies in the combination of inheritance, specialization by extension, and dynamic dispatching. The gain is in the amount of code that one does not have to write, simply because one can obtain it by inheritance from the previously defined software component. Inheritance was already present, in a rigid form, in the type derivation mechanism of Ada 83.

OOP brings to use the fundamental insight that types should be enriched by extending what they inherit, rather than being mere copies of their ancestors. Dynamic dispatching, that is to say the type-safe run-time choice of the operation that applies to a specific type extension, is real innovation in software construction.

Ada 95 implements OOP by a straightforward extension of the notion of derived type [Ada 95 Mapping Specification and Rationale (Version 4.1)]. Objects are no different here than in Ada 83: they are entities that can have values of a certain type. The message-passing concept can still be realized by means of tasks, but the more conventional concept of class is the Ada 95 type extension:

        type new_and_improved is new tried_and_true with record
             this: bell;
             that: whistle;
        end record;
        procedure ring(thing: new_and_improved) is ...
The type new_and_improved inherits the primitive operations of its parent type tried_and_true. Those operations act on the components that are common to both parent and child types. In addition, the new type contains additional components (bells and whistles) and one can define new operations, or override inherited ones, which make use of the components in the extension. There is no special syntax to designate objects of such types, nor to invoke operations that have formals of such types. Furthermore, if the parent operation has several parameters (or return value) of the parent type, then the signature of the inherited operation is obtained by replacing every mention of the parent type with the child type. As in most other OOP languages, Ada 95 makes some basic distinctions between types that can be extended in this fashion (tagged types) and types that cannot (e.g., integers).

In contrast, the notion of class in C++ derives from that of structure, and suggests that subprograms that apply to the objects of a class are, in some sense, part of those objects (class members). This model, inherited from Smalltalk and Simula, gives a privileged role to the first parameter of such a subprogram, which is invoked with a special notation as a qualified name. It is seldom useful to think of the operations as being part of a value, unless they are entries in a task. The Ada 95 model provides the benefits of OOP without this somewhat muddled notion of the object.

Note also that C++ classes are the only encapsulation mechanism in the language (if one excludes the use of files as modules) and, therefore, they also play the role that packages play in Ada. This dual role is better served with two separate notions.

Family Interactions

The Ada 95 model also provides a more natural description of binary operations and general operations that have several formals of the same tagged type. Consider:
          function merge(x, y: parent) return parent ;
          ...
          type child is new parent with record ...
          (function merge(x, y: child) return child is inherited)
In the C++ model, the member function has an implicit first parameter:
          class parent {
          ...
          parent merge(parent)
          ...
          }
          class child: public parent { ...
The class child inherits a function merge, whose second argument must be a parent (not a child) that returns a parent (not a child). Thus, the inherited operation loses the homogeneity of the original. Some complications arise; for example, the signature of a dispatching variant of merge must mention the parent type, not the child, otherwise it is a different operation. Because of the asymmetry, it is possible to subvert the dispatching mechanism and to call a dispatching operation with invalid arguments--and unpredictable results. In contrast, the Ada 95 dispatching model specifies that all dispatching parameters must have the same type, or more precisely, the same tag. A predefined exception is raised if this condition is violated.

It may be concluded that the Ada 95 model for type extension provides a cleaner definition of binary operations.

The Limits of Friendship

The notion of friend functions and friend classes is one of the more controversial aspects of C++. Its defenders correctly assert that the built-in privacy of class members that are not explicitly declared public means that it is impossible to write efficient code that makes use of two distinct classes A and B: a member function of A will only have access to the private representation of A and will have to use a procedural interface (with a potentially large execution penalty) to access the private representation of B. This is often unacceptable, as in vector-matrix multiplication, when each is defined as a separate class, forcing a large number of redundant index checks in the code. By selectively declaring certain functions as friends of a given class, the permission is granted to write code using the private representation from outside of the class.

Many programmers consider this mechanism unsafe and advise against its use. In Ada, the need for such a mechanism is lessened by the possibility of defining related types in the same package. These types can be private and yet, the functions defined in (and exported by) the packaged can make use in their bodies of the private representations of these types. This style respects the interface between interface and implementation, but requires more design discipline, as both types must be conceived simultaneously.

In Ada 95, an additional mechanism exists to create related entities in separate units: the hierarchical library model. Child packages can selectively request visibility of the private parts of the parent packages. This mechanism is as expressive as the C++ friend declarations and is independent of inheritance.

Keeping Track of Time

Ada was the first mainstream language to incorporate constructs for multi-tasking and for real-time programming. It is still virtually the only language to do so in a coherent fashion. Ada 95 has much more developed real-time and synchronization facilities, in particular, constructs for data synchronization (protected records). There are, as yet, no winning proposals for tasking facilities in C++ and no hint of notions of real-time and scheduling tools. In this area, C++ is a non-starter, even compared with Ada 83.

And yet C and C++ are used for programming real-time systems. How? It is done by using libraries in various stages of standardization, such as the CIFO proposed standard. Here again, 'it can be done' in C++, but only if these large libraries are regarded as part of the greater C++ system. Needless to say, as long as these are not standardized, programs that use them will be non-portable in subtle ways. A standard library of real-time primitives may eventually evolve and become part of C++, just as the stream facility has evolved from an interesting user-defined class to a recognized part of the language. Ada 95, however, has the advantage of features that are integrated from the beginning, with a well-understood interaction between complex features (e.g., tasking and exception). Ada 95 expands on the tasking facilities of Ada 83 with asynchronous transfer of control, protected records, better user access to scheduling primitives, additional forms of delay statements, and parametrized tasks.

In the area of real-time programming, Ada 95 is still without any serious competition.

Generics are Cheap and Reliable

To maximize software reuse, it is important to be able to parametrize software components, so that the same blueprint can be used in type-safe fashion for different applications. For example, a single sorting algorithm can be used for arrays of different element types, as long as the element type is ordered (i.e., has a comparison predicate). The generic facility of Ada is an excellent model of type parametrization. In addition to type parameters with specified operations (private and limited generic types), it is possible to specify type parameters that belong to a given class of types (e.g., any integer type), as well as value parameters and object parameters.

In contrast, C has no parametrization facilities. The common practice in C is to use the C preprocessor, a macro expander, to duplicate text with suitable replacement, in order to simulate generic instantiation. This mechanism is purely lexical: there are no syntax or semantic checks attached to the macro definition nor to any of its expansions.

The designer of C++ has recognized the need for a more disciplined parametrization mechanism, and the latest version of the language describes templates, a facility that is close in spirit to Ada generics. A template indicates that a unit has one or more class parameters. In the simple case, there are no operations defined on a class parameter, so it is equivalent to an Ada private generic type. It is possible to include function parameters that depend on a class parameter, and this is equivalent to the use of generic formal subprogram parameters in Ada. There is an asymmetry between class parameters and function parameters that is not present in Ada and the syntax forces repeated mentions of the template parameters (e.g., for each member function body of a class template).

Ada 95 has an even richer set of parametrization mechanisms than Ada 83. In Ada 95, it is possible to specify generic derived types, where the actual is any member of the class of the generic type, and generic formal packages, where the actual is any instantiation of the generic formal package. This latter form of parametrization is more powerful than what is available in C++ and is akin to the use of functions and signatures in ML. Both in terms of expressiveness and clarity, Ada 95 has a clear advantage over C++ here.

Multiple Inheritance

It might be argued that the multiple inheritance (MI) model of the current version of C++ is superior to what is proposed for Ada 95, but this is a most minor point. MI is a programming style, not a universal tool, and object-oriented practice of the last 10 years indicates that the critical benefit of OOP, namely code reuse, is not substantially enhanced by multiple inheritance. Without denying that some programming situations benefit from MI, it can be stated that its presence does not make or break an object-oriented language. Furthermore, the introduction of multiple inheritance complicates the type model and the implementation of dynamic dispatching, and brings about semantic difficulties that are solved by the introduction of virtual base classes, a substantial complexity in its own right.

A common use of multiple inheritance is to construct 'mixins,' but, in most examples, there is clearly a major and a minor parent: the minor provides additional functionality to a type that is clearly an extension of the major parent. Ada 95 programming techniques exist to obtain the effect of mixins by combining type extensions with renaming:

          type gizmo is ...
          procedure fiddle(g: in out gizmo);
          type enhanced is new old_and_reliable with record
              g: gizmo;
          end record;
             ...
          procedure fiddle(e: in out enhanced) is
          begin fiddle(e.g) ; end;
This is equivalent to a C++ declaration where enhanced is defined as a class that inherits both from old_and_reliable and from gizmo. It does require a few additional lines of Ada 95 code to obtain the same effect, but does not require a complex additional feature.

One other common use of multiple inheritance is to provide visibility. What would be done in Ada by the use of context clauses that name several packages, can be achieved in C++ by inheriting from several classes. Here again, classes play the role of Ada packages rather than that of types.

It can be concluded that by rejecting multiple inheritance, Ada 95 loses no significant functionality, but instead retains simplicity.

Appearance Counts

As acknowledged by all who have ever seen Ada code, Ada programs read well. The readability has a profound effect on program maintenance and was one of the obviously successful guiding criteria in the design of the syntax of Ada. It might be said that Ada reads well because it sounds right. This not to say that Ada programmers move their lips when parsing Ada code, but rather that reading carefully means hearing in one's mind. In doing so, it helps if what one hears is pronounceable in a very general sense. Ada text is dense in keywords, which some mistakenly consider noisy and redundant. In fact, those keywords act as natural punctuation marks and make program reading more natural. For example, a 'begin-end' bracketing is more natural-sounding than {..}. A few keywords have been added to the list from Ada 83 and others have been put to novel uses (e.g., with in type extensions), but the sound of the new language remains the same.

In contrast, C and C++ emphasize ease of writing, rather than ease of reading, resulting in a greater use of operators, the use of terser notations (the conditional expression, for example), and an absence of keywords (most noticeably in declarations). This makes C++ programs harder to transmit and to maintain and, therefore, more expensive.

Thus, seemingly minor considerations of surface syntax can have a profound effect on the economics of using one language or another. The readability of Ada is a substantial money-saver.

The Principle of Minimal Surprise

One of the most striking aspects of the C and C++ communities is the number of language idioms that are part of the programming culture. Idioms are typically short, cryptic expressions with great power, a notion most intensely developed in the APL community. The study of idioms has produced such entertaining books as The C Puzzle Book and it is clear that C and C++ programmers enjoy enormously the 'guess what this computes?' game. The question isn't fun, though, if the answer isn't in some way unexpected! These languages systematically violate the informal rule of design known as the principle of minimal surprise.

In contrast, this form of entertainment is completely missing from the Ada culture. Ada code does what it says, plainly. It is interesting to compare Exploring Ada with The C Puzzle Book: while legality is not discussed in the latter, a large number of the examples used in the former ask, 'is the code legal?' The Ada programmer may be frustrated when his code is rejected by the compiler, but he is much less likely to be surprised by the behavior of code that compiles without error.

Conclusion

To compare Ada 95 and C++ is not easy, if for no other reason than C++ is a language in flux, for which there exist no stable definition, approved standard reference, or translator validation suite. A glance at the list of additions to the language since version 2.0 (see Stroustrup's The C++ Programming Language) is sufficient to realize that active language design is still going on. The standardization mechanisms that were successful for Ada 83 ensured that Ada 95 became an international standard in a fairly short time frame, while C++ is still in the process. The Ada 95 standard has the active approval of all member countries interested in programming languages for software systems.

C++ is, for now, the promise of a good language. It is a number of related, but not identical, implementations that are being used fairly widely in the software industry. If the DoD experience of the last quarter century can be distilled to a single fact, it is that lack of portability and reusability are the most direct causes of runaway software costs. The strong standardization and the resulting portability of Ada code have proved their worth many times over the past decade. The use of a less well-defined tool would be a large step backwards.

This being said, it would disingenuous to deny the current success of C++ in the software community. This success is mostly due to the fact that C++ is much better than C and, for the legions of C programmers, this means a language that provides some measure of type checking, which is a large improvement over their previous standards. In addition, the classes of C++ are a convenient tool for data abstraction and information hiding (very much like Ada packages) and programmers are justifiably pleased to use them. Finally, C and C++ benefit from an environment that is written mostly in the same language, so that interface problems are few.

Although the C++ community would never state it so boldly, it does appear that C++ is, to some extent, a reaction to Ada. By extending C with some of the best ideas of Ada (strong typing, exceptions, generics), C++ did in some measure catch up to Ada. Ada 95 offers the chance to leapfrog C++ by extending the language in two critical areas: object-oriented programming and real-time programming. These improvements come to a language with already incomparable type safety (in contrast to the 'user beware' philosophy of C and C++) and a culture that is now 10 years mature and that yields cleaner and safer software than is produced with any other systems programming language. Ada 95 was well worth the wait.

References

Ada 95 Mapping Specification and Rationale, Version 4.1. Boston, MA: Intermetrics, March 1992.

Feuer, Allan R. The C Puzzle Book. Englewood Cliffs, NJ: Prentice-Hall, 1982.

Horstmann, Cay S. Mastering C++. New York: Wiley and Sons, 1991.

Mendal, Geoffrey O. and Douglas L. Bryan. Exploring Ada. Englewood Cliffs, NJ: Prentice-Hall, 1992.

Stroustrup, Bjarne. The C++ Programming Language, 2d edition. Reading, MA: Addison-Wesley, 1991.


This article is based on one written by Ed Schonberg.