C++
Chapter 3 - Functions

Function Prototype:

C++ is a strictly typed language.   As such the compiler requires that every function be fully prototyped.  A function prototype tells the compiler what type the function will return and the expected types in the functions parameter list.  The following is an example of a C/C++ prototype:

void
Vertex::Rotate ( float dx, float dy, float dz );

void is used to specify NO object will be passed or returned.   Although C++ will allow the absence of void, most programming standards require that void be used so as to create explicit and understandable code.

Vertex::Rotate indicates the the class the function Rotate is scope resolved to the class Vertex.

( float dx, float dy, float dz ) indicates that three floats are required by the function.

Although it is perfectly legal to leave out the identifiers for each passed parameter (dx,dy,dz), these identifiers provide an easily understandable interface.  The programmer should always strive to use identifier which provide the semantic of the use of the identifier.  The passed identifier should always vary in name to any attribute of the class

For Example: 

// The passed parameter x_val is different than the 
// class attribute x

class foo
{

  public:
    void set_x ( int x_val// accessor function
    {                         // to a private parameter
      x = x_val;
    }
  private:
    int x;
};

Function Definition:

The function prototype belongs in the class specification.   The function definition belong in the class implementation.  The above example illustrates a special case called automatic inlining of a function within a class specification.  This type of design is desirable for accessor functions as the function call is a short jump within the same code segment as the class definition.   Most all class methods will be prototyped in the *.h file for the class specification and the function definition will be in the *.cpp or implementation file.   It is a syntax error when the function prototype does not match the function definition

Prototype:

class Vertex
{
  ...
  void
  Move ( float dx, float dy, float dz );
}

Definition:

void
Vertex::Move ( float dx, float dy, float dz )
{
  x += dx; 
  y += dy;
  z += dz;
}

Functions as a Black Box:

A function ideally should have one incoming path and one out going path.  A function which allows multiple exit points may be very difficult to maintain and debug.  The following is poor design.  One should use "break" to exit logic structures like "while", "for" and "switch".

         Switch ( choice )
         {
             case 'a':
                return;            // The function return at this point not
             case 'b':            //  at the normal end of the function!
            
.....
         }


Scope Rules

Scope is the region in a program where an identifier is defined.   When an stack based variable drops out of scope, the destructor of the object is fired.

These are six different types of scope used within a program.

  1. Program scope:  This scope is associated with global variables.  Any identifier outside a function scope and placed before the "main" function retain global scope.  A goal in Object Oriented design is to eliminate all global variables.

    int version = 2;

    int main ( void ) { ... }

  1. File scope:  Identifier placed outside functions and classes have file scope.  If you wish to bind the a function or an identifier to a file then use the key word static on the identifier or function.  The function or identifier then becomes shared among all instances of the file, like #include would form.

    Modern C++  uses anonymous namespaces to achieve the File Scope binding.

    // file.cpp
    namespace
    {
        const double PI = 3.14159;  // file scoped variable
    }
    // eof file.cpp


    However, you will sill see the old style form...

    // file.cpp
    static char foo ( void )
    {
      std::std::cout << "File scoped function" << std::endl;
    }
    static const double PI = 3.14159;  // file scoped variable
    // eof file.cpp

  1. Function Scope:  Object or identifiers declared within a function block are known only within that function block.  When an object drops out of function or block scope the destructor will be fired.

    void foo ( int x, float y, char z )
    {

    }

  1. Function Prototype Scope:  Identifiers listed in a function parameters list in a function prototype are ignored by the compiler but should exist for understandability of the function interface.  Identifiers used within prototypes can be reused in a program without ambiguity.  However, identifiers used within a function definition retain function block scope.

    void foo ( int x, float y, char z );

  2. Block Scope:  Use of braces {} any where within a function, class or block scope create local scope for identifiers at that particular point in the program.

    ...
      {
        int i = 0;
      }

  1. Class Scope:  The is the scope bound to the class specification.  Identifiers bound by class scope exist as long as an instance of the class is active.

class foo
{

}

Static used within any scope makes that identifier or object shared by all instances of that scope.  Static identifiers of classes must be initialized in file or program scope.


Inline Functions

Inline functions can significantly increase the performance of a program.  Functions can be automatically inlined by including them into the class specification or the programmer can place the key word inline prior to a function definition.  The compiler will try to inline the function.  This means the compiler will try to place an instance of the function in the same code segment as the function call.  This locality of reference then requires only a short jump to the function rather than the standard long jump which most functions require.  Functions with repetition or recursion cannot be inlined.  The compiler will warn the programmer when a request for an inline function cannot be fulfilled by the compiler.

Examples of Macros, Inlines and Normal Functions.

The source code maccomp.cpp was compiled with varying level of optimization. 

Compiled with no optimization: output_none.txt
Compiled with Max Speed Optimization: output_maxspeed.txt


References and Reference Parameters

References are handle to objects in memory.  A reference to an object in memory is the size of the memory address to that object, so passing objects by reference is an efficient mechanism.  References are not traditional pointers.   They serve as a handle to an object in memory.  When an object is passed by reference, the receiving function operates directly on the object, but any operations effect the actual object passed NOT a copy as would normally occur in a pass by value situation

pass.cpp
// Example of passing by reference

#include <iostream>

using namespace std;

void
Sqr ( int x )  // pass by value, bitwise copy made here
{
  x = x*x; //operate on the copy
} // copy is destroyed when is drops from scope
void
Sqr_r ( int & x ) // pass by reference to memory
{
  x = x*x; //operate directly in the object
}

void
Sqr_p ( int * x ) // pass by pointer to memory
{
  *x = *x * *x; //operate directly in the object
}

int
main ( void )
{
  int x = 9;
  int y = 9;
  int z = 9;

  std::std::cout << "x = " << x << std::endl;
  std::std::cout << "Squaring By Value x" << std::endl;
  Sqr ( x );
  std::std::cout << "x = " << x << std::endl << std::endl;

  std::std::cout << "y = " << y << std::endl;
  std::cout << "Squaring By Reference y" << std::endl;
  Sqr_r ( y );
  std::cout << "y = " << y << std::endl << std::endl;

  std::cout << "z = " << z << std::endl;
  std::cout << "Squaring By pointer z..." << std::endl;
  Sqr_p ( &z );
  std::cout << "z = " << z << std::endl;

  return(0);
}

Figure 03_20

Default Arguments

Section 3.18 - Default arguments insure that parameters in function-prototype scope have a known default state.  This feature also allows the function to be called in a variety of ways.  This appears as if there are several different versions of the function.  Default arguments can take the place of function overloading and is a very efficient way to program.  Class constructors are perfect for using default arguments.

Default argument are place in the function prototype.

The following is an example of a constructor with default arguments.

class Vertex
{
  public:
    Vertex ( float _x = 0, float _y = 0, float _z = 0 );
}

Vertex::Vertex ( float _x, float _y, float _z  )
{
  x = _x;
  y = _y;
  z = _z;
}

A Vertex can be created in the following ways from one constructor:

    Vertex v1;
    Vertex v2( 112.2 )
    Vertex v3( 112.2, 200.3 );
    Vertex v4( 112.2, 200.3, 11.4 );

The rule for default arguments:
Once you start defaulting arguments in a function definition, you must keep defaulting arguments.
    void foo0 ( int x, int y = 0, int z = 0 );

The following examples break the rule.
    void foo ( int x = 0, int y );
    void foo1 ( int x, int y = 0, int z );

Figure 03_23


Unary Scope Operator

The unary scope resolution operator resolves a variable to global space.
  ::PI

// Using the unary scope resolution operator
#include <iostream.h>
#include <iomanip.h>

const double PI = 3.14159265358979;

int main()
{
   const float PI = static_cast< float >( ::PI );

   std::cout << setprecision( 20 )
        << "  Local float value of PI = " << PI
        << "\nGlobal double value of PI = " << ::PI << std::endl;

   return 0;
}

Function Overloading

Function overloading allows two functions with the same names but with different parameters to be defined in the same program.  This allow for a uniform interface for different type as the parameters.

For Example

void Show ( Vertex & v );
void Show ( Line & l );

Rule for function overloading:

Overloaded functions are functions with the same name but vary by more than the return type.
The following example is an invalid overload function as it breaks the rule.

    int  Show ( Vertex & v );
    void Show ( Vertex & v);
// Using overloaded functions
#include <iostream.h>

int square( int x ) { return x * x; }

double square( double y ) { return y * y; }

int main()
{
   std::cout << "The square of integer 7 is " << square( 7 )
        << "\nThe square of double 7.5 is " << square( 7.5 ) 
        << std::endl;    

   return 0;
}

Figure 03_25


Function Templates

A function template is the ability to specify a type independent function.   When the compiler identifies the use of this function where an instance is not already in place, the compiler will create the code to fulfill the specified types.   So older compiler may require you to place template into header files for guaranteed inclusion local to the function call and/or you will have to prototype the functions prior to the main function so the compiler creates the template function before they are referenced.

Template Swap Function

template <class mytype>
mytype
Swap ( mytype & a, mytype & b )
{
  mytype temp;
  temp = b;
  b = a;
  a = temp;
}

// Fig. 3.27: fig03_27.cpp
// Using a function template
#include <iostream>

template < class T >
T maximum( T value1, T value2, T value3 )
{
   T max = value1;

   if ( value2 > max )
      max = value2;

   if ( value3 > max )
      max = value3;

   return max;
}

int main()
{
   int int1, int2, int3;

   std::cout << "Input three integer values: ";
   cin >> int1 >> int2 >> int3;
   std::cout << "The maximum integer value is: "
        << maximum( int1, int2, int3 );          // int version

   double double1, double2, double3;

   std::cout << "\nInput three double values: ";
   cin >> double1 >> double2 >> double3;
   std::cout << "The maximum double value is: "
        << maximum( double1, double2, double3 ); // double version

   char char1, char2, char3;

   std::cout << "\nInput three characters: ";
   cin >> char1 >> char2 >> char3;
   std::cout << "The maximum character value is: "
        << maximum( char1, char2, char3 )        // char version
        << std::endl;

   return 0;
}

04/12/00 03:51 PM