next up previous
Next: Complex numbers Up: Scientific programming in C Previous: Random numbers

C++ extensions to C

In this subsection, we shall briefly discuss some of the useful, non-object-orientated extensions to C introduced in the C++ language. Files containing source code which incorporates C++ elements should be distinguished from files containing plain C code via the extension .cpp.

In C, all local variables within a function must be declared before the first executable statement. In C++, this restriction is relaxed: a local variable can be declared (almost) anywhere in a function, provided that this declaration occurs prior to the variable's first use. The following code snippet illustrates the use of this new feature:

 
. . .
for (int i = 0; i < MAX; i++)
 {
  . . .
 }
. . .
Observe that the index i of the for loop is now declared at the start of the loop, instead of at the start of the function containing the loop. This is far more convenient, and also makes the code easier to read (since we no longer have to skip back to the declarations at the start of the function to check that i is an int). Note, however, that the variable i is only defined over the extent of the loop (i.e., between the curly braces). Any attempt to reference i outside the loop will result in a compilation error. In general, when a variable is declared in C++ its scope (i.e., range of definition) extends from its declaration to the closing curly brace which terminates the current program block. Program blocks are functions, loops, conditionally executed compound statements, etc., and are delineated by curly braces. There are a number of restrictions to this new method of variable declaration. Variables cannot be declared within conditional statements, in the second or third expressions of for loops, or in function calls.

In C, we have seen that in order to pass an argument to a function in such a manner that changes made to this argument within the function are passed back to the calling routine, we must actually pass a pointer to the argument. This procedure, which is called passing by reference, is illustrated in the code snippet listed below:

. . .
void square(double, double *);
. . . 
int main()
{
  . . .
  double arg, res;
  square(arg, &res);
  . . .
  return 0;
}
. . .
void square(double x, double *y)
{
  *y = x * x;
  return;
}
Here, the second argument to square() is returned to main() in modified form. This argument must, therefore, be passed as a pointer. Consequently, we must write &res, rather that res, when calling square(), and we must refer to the argument as *y, rather than y, in the function itself. After a while, all these ampersands and asterisks can become a little tedious! C++ introduces a new method of passing by reference which somewhat less involved. This new method is illustrated in the following:
. . .
void square(double, double &);
. . . 
int main()
{
  . . .
  double arg, res;
  square(arg, res);
  . . .
  return 0;
}
. . .
void square(double x, double &y)
{
  y = x * x;
  return;
}
Here, the second argument to square() is again passed by reference. However, this is now indicated by prepending an ampersand to the variable name in the function declaration. A corresponding ampersand appears in the associated function prototype. Note that we do not need to explicitly pass a pointer to the second argument when calling square() (this is done behind the scenes): i.e., we write res, rather than &res, when calling square(). Likewise, we do not have to explicitly deference the argument in the function itself (this is also done behind the scenes): i.e., we refer to the argument by its regular local name, y, as opposed to *y, within the function.

Functions are used in C programs to avoid having to repeat the same block of code in many different places in the source. The use of functions also renders a code easier to read and maintain. However, there is a price to pay for the convenience of functions. When a regular function is called in an executable, the program jumps to the block of memory in which the compiled function code is stored, and then jumps back to its original position in memory space when the function returns. Unfortunately, the large jumps in memory space associated with a function call take up a non-negligible amount of CPU time. Indeed, the overhead associated with making function calls often discourages scientific programmers from writing small functions, even when it may be desirable to do so. C++ provides a way out of this dilemma, via the use of the new keyword inline. An inline function looks like a normal function when it is used, but is compiled in a different manner. Calling an inline function from several different locations in a code does not result in multiple calls to a single function. Instead, the code for the inline function is inserted into the program code by the compiler wherever the function is used.

Inline functions are only useful for small functions. The disadvantage of inserting the code for a large function multiple times into the code for a typical program easily outweighs the small gain in performance obtained by the elimination of standard function calls. The break-even point for inline functions is usually about three executable lines.

To inline a function, a programmer adds the keyword inline at the start of the function's definition. For example:

inline double square(double x)
{
  return x * x;
}

Because the body of an inline function must be known before the compiler can insert it into the program code, wherever the function is used, we must define such a function prior to its first use--a prototype declaration is not enough. It is common practice to define inline functions at the same location in source code files that the prototypes for regular (i.e., outline) functions are placed.

Variable size array declarations of the form

void function(n) 
{
  int n;
  double x[n];
  . . .
}
are illegal in C, which is extremely inconvenient. In C++, such declarations are enabled via the use of the new keywords new and delete. Thus, the C++ implementation of the above code snippet takes the form:
void function(n) 
{
  . . .
  int n;
  double *x = new double[n];
  . . .
  x[i] = . . .
  . . .
  . . .
  delete x[];
  . . .
}
Note that x is actually declared as a pointer, rather than a standard array. The declaration new double[n] reserves a memory block which is just large enough to store n doubles, and then returns the address of the start of this block. The line delete x[] frees up the block of memory associated with the array x when it is no longer needed (note that this is not done automatically). Unfortunately, the new and delete keywords cannot be used to make variable size multi-dimensional arrays.


next up previous
Next: Complex numbers Up: Scientific programming in C Previous: Random numbers
Richard Fitzpatrick 2006-03-29