JAN89: COMPARING MODULA-2 AND C++

Scott Ladd is a full-time freelance computer journalist. You can reach him at P.O. Box 61425, Denver, CO 80206.

Two of the hottest languages these days are Modula-2 and C++. They appeared in the early 1980s, and both are gaining in popularity. Each was designed and written by a single person: Niklaus Wirth created Modula-2, and C++ was developed by Bjarne Stroustrup. An additional similarity is that both languages are extended versions of earlier languages. This article contrasts Modula-2 and C++; it assumes you have a cursory familiarity with both Pascal and C (the root languages of Modula-2 and C++, respectively).

Pascal was designed as a teaching language, to show students how to structure programs properly. It lacks intrinsic support for character strings, file I/O, and separately compiled modules. Pascal has many features that are useful in software development, and the language gradually gained popularity outside the academic community. Unfortunately, the vendors that implemented Pascal did not develop a standard for extending the language to cover its weak points. Thus, every Pascal compiler is unique, and porting programs between implementations is difficult.

Wirth intended Modula-2 to be the successor to Pascal. Modula-2's name implies one of its basic concepts: modular program design. Through the use of modular facilities, Modula-2 supports data abstraction and encapsulation. All I/O is done through procedures in modules as an aid to portability. As long as the interface to the I/O procedures does not change, the implementation of those procedures can be modified for different environments without having to make alterations in the programs using them.

Among high-level languages, only Modula-2 and Ada support multiprocessing. In addition, Modula-2 supports bit manipulation, generic types, and system-level access. Modula-2 (unlike Pascal) is well suited to the writing of system software such as operating systems and device drivers.

C is a powerful system-level language; in its original definition, however, it lacked many features necessary for large, complex projects. Stroustrup designed C++ to amend problems with the C language, in much the same way as Wirth designed Modula-2 to correct the deficiencies of Pascal. C++ provides capabilities for strong type checking, modular programming, and data abstraction while maintaining full compatibility with existing C programs.

Stroustrup borrowed the object-oriented paradigm from Smalltalk; classes and methods are the most significant additions he added to C in creating C++. In an object-oriented language, objects (data items) belong to classes (types), which have associated methods (functions). "Messages" are sent to objects via their class methods; the messages tell the objects how to act. For example, an object of class int would be given the message "add 1" by the ++ (increment) method. Although this concept can take some getting used to, it can be more appropriate than traditional program design methods.

C++ is often implemented as a translator. The C++ translator works much like the standard C preprocessor, converting C++ programs into C programs. A C compiler is then run on the output of the translator, producing executable code. By its nature, this process is cumbersome and slow. Recently, manufacturers have released true C++ compilers.

In addition to adding object-oriented capabilities to C, C++ offers other enhancements, including in-line functions, function prototyping (since added to the ANSI standard), and overloading. Because it retains all C's low-level facilities, C++ can be used for a wide variety of applications.

Modula-2 and C++ have the following general features:

Source code format -- Source code is case sensitive in C++ and Modula-2. All C++'s keywords must be in lowercase; Modula-2 requires uppercase.

Data types -- Both languages are rich in data types and allow the creation of new types. They offer long and short, signed and unsigned, integers and reals. Strings in both languages are handled as arrays of characters terminated by a zero (NUL) byte. Both languages have pointer types. C++ provides the capability to smoothly integrate new types through its class structure, while Modula-2 provides generic types and better control over data item visibility. Modula-2 provides SET types (including BITSET, which allows access to the individual bits of an item), whereas C++ has C's bit structures.

Functions and procedures -- Modula-2 has procedures, whereas C++ has functions. The purpose is the same in both languages: to provide callable routines with parameters and local variables. Modula-2 allows the nesting of procedures within procedures, using the same scoping rules as it does for variables. Modula-2 and C++ parameters can be passed either by reference or by value.

Control structures -- Here, too, there are no important differences. Although the syntax may vary, the capability is the same. Both languages have loops with tests at both the top and the bottom of the loop. The for, which iterates through a succession of values, is available in both languages. There is (of course) the ubiquitous if ..else ..endif conditional construct. Useful multiple-condition branches (switch in C++ and CASE in Modula-2) are available.

Library routines -- C++ uses the standard C library, which is extensive and robust. Wirth defined several standard modules in his definition of Modula-2, but these provided only minimal capabilities. Most Modula-2 vendors have created expanded libraries for their implementations.

Modula-2 is not a superset of Pascal; rather, it is an evolution of the earlier language. For example, one of Modula-2's most welcome enhancements over Pascal is its simplified block structure. A Modula-2 program is not filled with BEGIN...END pairs; each control structure has an implicit block terminated by an END statement. Where Pascal has functions and procedures (the former returns a value whereas the later does not), Modula-2 simply has procedures. A Modula-2 procedure returns a value if it is specified as doing so.

One fascinating feature of Modula-2 is its support for coroutines, which are individual processes within a program that run concurrently. This allows multiple tasks (within a program) to be performed simultaneously. A word processor, for example, could use coroutines to allow a document to be printed while another is being edited.

The strong type checking in Pascal prevents spurious errors but also causes difficulties. For instance, it is impossible to create a general function in standard Pascal that can accept arrays (such as character strings) of different sizes. Modula-2 introduces the concept of the open array parameter -- for example, a parameter to a procedure can be declared as an array without bounds (array of char). Any length array of the specified type (in this case, a character array) can be passed to that function. The procedure can then find out the actual length of the array with the built-in HIGH procedure.

Modula-2 also provides the generic type. A WORD type is equal in size to the default word size of the hardware it is running on. In the case of MS-DOS, a WORD type is 16 bits long. Any type of the same size (usually the INTEGER and CARDINAL types) is assignment compatible with WORD. An extension of this is that any value can be passed to an open array of type WORD (that is, a procedure parameter of type ARRAY OF WORD). The size of the array is the number of WORD types required to hold the value being passed.

The concept of modules allows Modula-2 programmers to control access to individually compiled portions of a program. The following object-oriented example illustrates how modules can be used.

C++ refines and expands C while including all C's strengths. Several features of the emerging ANSI C standard are actually borrowed from C++. Examples of this are function prototypes, const, and void. Function prototypes were added so that C++ could do type checking or arguments; under the original K&R C, values of incorrect types could be passed to functions, often causing obscure errors. Const provides named constants. Adding void allowed the creation of generic pointers and made it possible to declare that a function does not return a value.

Overloading functions lets the programmers create several functions with the same name but differentiated by their parameters. Instead of C's current crop of absolute value functions (abs, dabs, fabs, and labs), a C++ implementation could have the following:

overload abs; int abs(int i); long abs(long l); float abs(float f); double abs(double d);

At compile time, the C++ compiler determines which version of abs to use based on the parameters it is being passed. This facility can make programs easier to understand.

It is possible to overload operators in C++. You can, for instance, create a new class that can use the same operators as existing classes. The section on object-oriented programming later in this article contains an example of operator overloading.

The object-oriented features of C++ are prominent. Object-oriented programming requires a change in thinking for many programmers; instead of thinking in terms of the nuts and bolts of programming, programmers are required to visualize a program as a series of processes applied to data items. This may seem to be a subtle distinction, but it must be mastered to truly appreciate and use an object-oriented language. The object-oriented features of C++ are illustrated in the section on the subject in this article.

C++ provides dozens of other extensions to C, including in-line functions, anonymous (unnamed) unions, and default function parameter values. There are dangers to the many features provided by C++. Whereas C has always been famous for providing the rope by which programmers hang their programs, C++ adds the noose. Care must be taken to avoid "going wild," especially with the overload capabilities.

An object-oriented language is extensible, which means the programmer can create new data objects that are integrated into a program. In order to show how each language is used to create a new class of objects, I have implemented complex numbers in each language. Complex numbers are a superset of the real numbers, having both a real and an imaginary part. Complex numbers are used in a number of scientific calculations; this is one reason why Fortran directly supports complex numbers.

Both implementations are identical in function. First, you need to define the data elements of a complex number. In this case, the complex number has two floating-point components for its real and imaginary parts. Then, you determine what operations can be performed on complex numbers. In the example, the allowed operations are assignments, addition, subtraction, multiplication, division, and output. I used Zortech C++, Version 1.06, and JPI TopSpeed Modula-2, Version 1.11, to develop the examples.

In C++, a class is generally developed using two files. A header file contains the class description, and a source file holds the actual implementation. The example in Listings One - Three, page 102, follows this form.

A class in C++ looks like a structure; in fact, a structure is a special form of class. Those items listed in the private section cannot be accessed outside the class definition. Public data and functions are available for use outside the class scope. The example has only two private items, which are the two floating- point components of a complex value. (See Listings Four - Six, page 104.)

A "constructor" in C++ is called whenever an object of the class is created to initialize the object. The counterpart of a constructor is a "destructor," which can be used to deallocate any resources used by an object (once that object is no longer needed). The example does not require or implement a destructor.

Function overloading is used to provide several different constructors. Each constructor allows an object of class complex to be declared with different initialization values. The type and number of the initializers determines which constructor is used. The following code fragment shows how C++ determines which constructor to use:

complex a; /* no values; uses 1st constructor */ complex b(2.0,5.0); /* two real values; uses 3rd constructor */ complex c(b); /* complex value; uses 2nd constructor */

Note that the first constructor is defined in the class definition; there is no external function for it. This constructor is compiled into in-line code, avoiding the overhead of a function call when declaring complex values without initializers.

Modula-2 does not support a specific class structure, but the same effect can be created through the use of modules. Modula-2 uses two files to create modules called the definition and implementation modules. The definition module defines the interface to the data elements and procedures stored in the implementation. Unless it is defined in the definition module, an item is private to the implementation.

In order to prevent programmers from manipulating the component values of a complex item directly, an "opaque type" is used. The definition module lists the type COMPLEX (allowing programs to create items of that type) but does not expose the type's internal structure. Because Modula-2's opaque types must be pointers, the implementation module defines the COMPLEX type as a pointer to a structure containing two real values. The situation requires the functions Create and Destroy in order to allocate and deallocate space for the complex numbers. These are similar to C++ constructors and destructors, but they must be called explicitly by the program.

Modula-2 does not support overloading, so there is no way to assign functions to infix operators. C++ allows functions to be assigned to operators, so in C++ it is possible to say:

a = b + (c * d);

Modula-2 requires a less readable construct:

Multiply(temp1,c,d); Add(a,b,temp1);

The process is identical; both languages use functions to simulate operators. The primary difference is that C++'s notation is more natural, making it easier for programmers to discern what is happening. Although the results are the same, most programmers would prefer the clarity of the C++ version.

Both implementations provide a function for displaying the value of complex number. The Modula-2 version uses a procedure that calls upon standard library procedures. In the C++ version, a function is declared that accesses the stream output functions of C++. The stream class is provided with C++. The << (left shift) operator has been overloaded in C++ to say "send the object on the right to the object on the left." Streams can be easier to use for simple output than library function calls. Complex numbers are displayed using built-in stream output functions for characters, strings, and floating-point numbers.

C++ offers other object-oriented features not shown in the example. New classes can be "derived" from other, pre existing classes, and characteristics can then be "inherited" from the original class.

C++ and Modula-2 are young languages. Modula-2 has been more widely implemented; on the other hand, it has had slightly longer to get into the mainstream. C++ is just beginning to come into its own outside the Unix/AT&T environment. Currently, half a dozen Modula-2 compilers are available for the PC, whereas three C++ preprocessors and one C++ compiler are available. In the Macintosh world, Apple has announced that it will extend its C compiler to be a C++, and there are at least two Macintosh Modula-2 compilers.

The situation is similar when it comes to third-party add-on libraries. I know of only one company that provides object libraries for C++. There are a few companies that market products for Modula-2. Recently, several vendors of C libraries have begun to market Modula2 versions of their products. As the popularity of these products grows, so will the third-party support.

Modula-2 and C++ are powerful languages; both have bright futures. C++'s classes, methods, and overloading make it a powerful tool, but it has few restraints to keep programmers from being too "creative." Modula-2 is a strongly organized language with restraints to keep programmers within guidelines. It is not as extensible as C++, but it does implement its own unique features, such as SETs, coroutines, and open arrays. Which of these languages is more appropriate for a specific project will depend on the type of application and the experience of the programmers involved. Both languages are powerful and interesting to work with.

1. Stroustrup, Bjarne. The C++ Programming Language. Englewood Cliffs, NJ.: Addison-Wesley, 1986.

2 Wirth, Niklaus. Programming in Modula-2. 3rd ed. New York,: Springer-Verlag, 1985.

_C++ VERSUS MODULA-2_

by Scott Ladd