ANSI standard C introduced function prototypes to the C language. A function prototype defines the type of each function argument and the function's return value (the function's signature). For example:
/* A forward declaration to a_function */ int a_function(float b, double c);
/* The definition of a_function */ int a_function(float b, double c) { /* Function body */
}
C++ requires that function prototypes be provided for all functions before they are used and enforces consistent function use between program files. Thus, it is possible to distinguish between functions that have the same name but different signatures. C++ uses this capability to allow function names to be overloaded. That is, more than one function can be defined with the same name; the compiler compares function call arguments with function signatures to determine which version to use.
In C programs, the library routines malloc and free are used for dynamic memory allocation.
C++ defines two additional operators, new and delete, as illustrated in the following code
fragments.
struct S {
/* Structure body */ };
S *sPtr = new S; /* Allocate instance of S */ delete sPtr; /* Delete instance of S */
int *iPtr = new int[25]; /* Allocate array of integers */ delete [] iPtr; /* Delete array of integers */
Notice that new is given a description of the type of the data object to be allocated; it returns a
pointer to dynamically allocated data of that type. The delete operator is used to release
dynamically allocated storage. The programmer must indicate when an array of objects is being deleted.
5.1.2 Classes
The most significant feature that C++ adds to C is the concept of classes. A class can be thought of as a generalization of a C structure. In C, a structure groups together data elements of various types under a single name; in C++, structures can also contain member functions. Like data elements of a C structure, member functions of a class can be accessed only through a reference to an object of the appropriate type. In C++, a class defines a scope in which names referring to functions and data can be defined. Classes can be introduced using the C keywords struct and union or the C++ keyword class.
Program 5.1 illustrates various features of the C++ class mechanism. This program defines a class named Datum containing a data member x, a member function get_x, and two constructor
functions. (Notice the C++ single-line comments; anything after a double slash // is a comment.)
These terms are defined in the following discussion.
The syntax Datum::get_x() is used to name a member function get_x of Datum. This name,
called a quantified name, specifies that we are referring to a function defined in the scope of
Datum. If we do not quantify the name, we are defining the global function get_x(), which is a
different function. Notice that within the definition of Datum::get_x() we can refer to the data
member x directly, because x and get_x are defined in the same scope. We also could incorporate
the definition for function get_x directly in the class definition, as follows. public:
int get_x() { return x; } ...
The two member functions named Datum are constructor functions for the Datum class. A
constructor has the same name as the class to which it applies and, if defined, is called whenever an object of the appropriate type is created. Constructor functions are used to perform initialization and can be overloaded.
The function test in Program 5.1 creates and uses three Datum objects, two of which are declared
in the first two lines in the function body. Notice that the class name Datum can be used directly; in
C we would have to write struct Datum. In the third line, the new operator is used to allocate the
third Datum object.
Because constructors have been defined for Datum, they will be called whenever Datum objects are
created. The constructor with no arguments, called a default constructor, is called when a_datum
is created, thereby initializing the field x of a_datum to zero. The declaration of another_datum
and the new operator both specify an integer argument and hence use the second constructor,
thereby initializing the variable x to 23 in these two cases.
Recall that in C, the fields of a structure are accessed by using the dot operator (struct.fieldname), while the fields of a structure accessible via a pointer are accessed with the
arrow operator (structptr->fieldname). As illustrated in the function test, these same
mechanisms can be used to refer to the member functions of a C++ class.
The C++ class mechanism also supports protection. Members of a C++ class can be designated as being either public or private. A public class member can be used without restriction by any
program that has a reference to an instance of a class. Public data members can be read or written, and public member functions may be called. In contrast, private members can be accessed only from within the class object. Private data members can be accessed only by a class member function, and private member functions can be called only from within another member function of the class. For example, the variable x in the Datum class is a private variable and hence can be
accessed by the member function get_x but cannot be referenced directly as a_datum.x.
5.1.3 Inheritance
The final C++ feature described here is inheritance. As in C, a class or structure can be included as a member of another class, hence defining a has-a relationship between the two classes. In C++, inheritance is used to create an alternative relationship between classes, an is-a relationship. If a class D inherits from class B, then all public members of B are also members of D. We say that D is derived from B, and that D is a derived class while B is a base class. D includes all public members of B and may also include additional members, which are defined in the usual way. We can view D as being a specialized version of a B, hence the is-a relationship.
Program 5.2 illustrates the use of inheritance. The syntax for inheritance is to specify a list of base classes after the derived class name. The base class list is separated from the derived class name by a colon. The keywords public and private are associated with the base class names to specify
whether the inherited members are to be public or private members of the derived class.
Members of the base class can be redefined in the derived class. For example, in Program 5.2 class D redefines func2. When func2 is called from an object of type B, we access the version of func2
defined in B. If func2 is called from an object of type D, we get the version of func2 defined in
D.
In some situations, we may want a base class to call functions that are defined in a derived class. This facility is supported by a C++ mechanism called virtual functions. A function declared virtual in a base class can be defined in a derived class. This feature, which allows a programmer to specialize a generic base class for a specific application, is used in Section 5.8.2 to build a reusable parallel library.
5.2 CC++ Introduction
CC++ is a general-purpose parallel programming language comprising all of C++ plus six new keywords. It is a strict superset of the C++ language in that any valid C or C++ program that does not use a CC++ keyword is also a valid CC++ program. The CC++ extensions implement six basic abstractions:
1. The processor object is a mechanism for controlling locality. A computation may comprise one or more processor objects. Within a processor object, sequential C++ code can execute without modification. In particular, it can access local data structures. The keyword global identifies a processor object class, and the predefined class proc_t
2. The global pointer, identified by the type modifier global, is a mechanism for linking
together processor objects. A global pointer must be used to access a data structure or to perform computation (using a remote procedure call, or RPC) in another processor object. 3. The thread is a mechanism for specifying concurrent execution. Threads are created
independently from processor objects, and more than one thread can execute in a processor object. The par, parfor, and spawn statements create threads.
4. The sync variable, specified by the type modifier sync, is used to synchronize thread
execution.
5. The atomic function, specified by the keyword atomic, is a mechanism used to control the
interleaving of threads executing in the same processor object.
6. Transfer functions, with predefined type CCVoid, allow arbitrary data structures to be
transferred between processor objects as arguments to remote procedure calls.
These abstractions provide the basic mechanisms required to specify concurrency, locality, communication, and mapping.
Example . Bridge Construction:
Program 5.3 illustrates many CC++ features. It is an implementation of the bridge construction algorithm developed in Example 1.1. The program creates two tasks, foundry and bridge, and
connects them with a channel. The channel is used to communicate a stream of integer values
1..100 from foundry to bridge, followed by the value --1 to signal termination.
While the concepts of task and channel are not supported directly in CC++, they can be implemented easily by using CC++ mechanisms. Hence, the main program creates two tasks by first using the new operator to create two processor objects and then using the par construct to
create two threads, one per processor object. The two tasks engage in channel communication by invoking functions defined in a Channel class, which will be described in Section 5.11. A channel
is declared in the main program and passed as an argument to foundry and bridge. These
processes use access functions get_out_port and get_in_port to obtain pointers to out-port and
Next, we give a more complete description of CC++. The presentation is loosely structured according to the design methodology of Chapter 2. First, we describe how CC++ programs specify concurrent execution. Then, we explain how CC++ programs specify locality. Next, we explain how to specify various communication structures. Finally, we describe how CC++ programs specify mapping. Along the way, we also address the issues of modularity and determinism. A CC++ program, like a C++ program, executes initially as a single thread of control (task). However, a CC++ program can use par, parfor, and spawn constructs to create additional
threads. A parallel block is distinguished from an ordinary C++ block by the keyword par, as
follows. par { statement1; statement2; ... statementN; }
A parallel block can include any legal CC++ statement except for variable declarations and statements that result in nonlocal changes in the flow of control, such as return.
Statements in a parallel block execute concurrently. For example, the following parallel block creates three concurrent threads: two workers and one master.
par {
worker(); worker(); master(); }
A parallel block terminates when all its constituent statements terminate; execution then proceeds to the next executable statement. Thus, in the preceding parallel block, the thread that executed the parallel block proceeds to the next statement only when both the master and the workers have terminated.
A parallel for-loop creates multiple threads, all executing the same statements contained in the body of the for-loop. It is identical in form to the for-loop except that the keyword parfor replaces for. For example, the following code creates ten threads of control, each executing the function myprocess.
parfor (int i=0; i<10; i++) { myprocess(i);
}
Only the loop body of the parfor executes in parallel. Evaluation of the initialization, test, and
update components of the statement follows normal sequential ordering. If the initialization section uses a locally declared variable (for example, int i), then each instance of the loop body
has its own private copy of that variable.
CC++ parallel constructs can be nested arbitrarily. Hence, the following code creates ten worker
par {
master();
parfor (int i=0; i<10; i++) worker(i);
}
Finally, the spawn statement can be used to specify unstructured parallelism. This statement can be
applied to a function to create a completely independent thread of control. The parent thread does not wait for the new thread to terminate execution, and cannot receive a return value from the called function. As we shall see in Section 5.10, one use for the spawn statement is to provide an
efficient implementation of RPCs that do not require a return value.
5.4 Locality
In the task/channel programming model of Part I, the concepts of locality and concurrency are linked: a task is both a separate address space and a thread of control. In CC++, these two concepts are separated. Processor objects represent address spaces, and threads represent threads of control. Processor objects can exist independently of threads, and more than one thread can be mapped to a processor object.