Introduction:

Header files are an integral part of the C++ language in that they essentially define the programming interface available to a user. ISIP header files obey a strict organization scheme so that users know exactly where to look for certain things. Four good examples of standard ISIP header files are:

a scalar math class that implements a Boolean variable (basic);
a vector math class that implements a vector using C++'s template capability (intermediate);
a data structure class that implements a doubly linked list using templates (intermediate);
an algorithm that computers Autocorrelation coefficients (advanced).

In this section, we explain those constructs that are somewhat unique (or important) to C++ that you will find in our header files.

For a good overview of header files, see Lippman and Lajoie, Section 8.2.3.

Conditionals:

At the top of each header file, you will see something similiar to the following code:

This code guarantees this include file will only be included once per compile. The first time the compiler passes through this code, the name ISIP_BOOLEAN will not be defined, so the class will then be defined. The second time through, ISIP_BOOLEAN will already be defined, so the code will be skipped.

Since we use many header files in a typical large-scale C++ application, some of which might be templates thousands of lines long, this construction is very important because it minimizes the amount of time the compiler spends re-processing header files it has already seen.

Similar conditionals to those shown above begin each header file, but for the conditional to work properly, you must also place an ending delimiter in your code:

Each #ifdef and #ifndef block is end-bounded by an #endif directive. The compiler looks for the #endif line just like it does the last curly brace in a function.

For a more detailed discussion of conditionals, see Lippman and Lajoie, Section 1.3.0.

Forward Class Declarations:

Often we need to reference a class as an argument in a function that appears in another class. See Sdb for a simple example, and Graph for an example involving templates. A typical function declaration that accepts an object as an argument might look something like this: If any class only appears as an argument in a function, and is not used as data, inherited, or referenced in any other such way, we can use a forward class declaration rather than including the header file (and thereby defining the class): One advantage of this is that we save an unnecessary include during compilation. Another advantage is that we make the dependencies between classes clear.

For an explanation of when to use a class declaration, see Lippman and Lajoie, Section 13.1.5.

Class Definitions:

When we use forward class declarations, we make a pact with the compiler that we will actually define the class at some other place in the code. See Boolean for an example of a completed class definition, and Graph for an example of a template class definition. Our class definitions follow a strict format that consists of five sections which always appear in the order shown below: The comments for each section are accurate indicators of what you will find in each section, be it data or methods. Only the data and methods in the public section of the class definition are visible to main programs that use the class. Data and methods in the protected sections are visible to all inherited classes, and all methods within this class. Private sections are only visible to methods within this class. In general, all internal class data is declared as protected, in order that classes can easily build on this class using inheritance.

Classes wrap data and methods together. This is perhaps the most important feature of C++. Any method in this class has access to its data, yet other classes only can access those things the designer decides should be accessed. Hence, programmers can provide a stable interface, and clashes of names within a program's namespace are avoided (two classes can have internal data named "Signal" and not interfere with each other). Classes also protect your data from accidental corruption by faulty code and make your code easier to read and understand.

For a more detailed explanation of classes and how to use them, see Lippman and Lajoie, Chapter 13.

Inheritance:

When you've written a great class, sometimes you recognize another problem that could be solved by a similar class. Rather than writing an entirely new class to solve the new problem, you can derive the new class from your existing class. The new child class inherits all the methods and member data of the parent class, and can define specialized methods to better solve the new problem. See VectorByte for an example of a derived class, and MVector as an example of a parent class.

The class definitions of derived classes look like this: where the definition of the parent class (a template in this case) looks like this: When you derive one class from another, all of the methods of the parent class are copied into the child class. If any methods from the child class overlap the names of methods in the parent class, the child's methods override the parent's methods. The same rule holds for member data. Unless you specify otherwise, the sections of the parent class are inherited into the child class at the same access level (public, protected, private). So public methods of MVector are also public methods in VectorByte.

For more information on class inheritance, see Lippman and Lajoie, Section 17.1.1. You should also read over the sections on virtual functions, Lippman and Lajoie, Section 17.5.

Templates:

C++ is a strongly-typed language, meaning that when you write a class or function you must specify the data types it can use. Furthermore, you must only use the class or function with the data types specified, unless appropriate conversion rules have been included. Certain classes, particularly data structures, are best implemented in a way such that they can operate on any user-specified data type. Such classes are often called container classes. C++ provides a mechanism know as templates to implement such a capability. See MScalar for an example of a template class definition.

Templates allow you to build standardized data types out of a single, generic class. For example, we built a floating-point type that is guaranteed to be thirty-two bits long using the MScalar class. For this derivation, the class definition begins: We also built a generic vector class from which we derived standard-sized vector types to complement our scalar classes. The class definition for VectorFloat (a vector of thirty-two bit floating-point numbers) begins: As you can see, template classes add great flexibility to your code. However, you lose the benefits of templates unless you write your methods to accept the types the template sends them.

In the MScalar header file, you see the template class definition that begins: and some function prototypes that look like this: These function declarations will accept the type given to the class when it is instantiated. That is, if a MScalar object is declared for use as a long integer, the instantiation will look like this: For a more thorough coverage of templates see Lippman and Lajoie, Chapter 16.

Access Restrictions:

An important part of the object-oriented programming paradigm is the definition and control of a user interface - or application programming interface (API) as it is often called. In order to prevent users from deviating from the standard interface, programmers define what portions of the class can be accessed by particular types of users, and place all important code behind access restrictions. As you can see in the header file for Boolean, access is controlled through the use of the keywords public, protected, and private.

The public section constitutes the users' interface to the class. In an object-oriented environment, the interface methods do not implement the actual algorithms for the class, but call protected or private methods that do implement the algorithms. Our classes have three public sections: one for constants required by the environment, one for interface methods common to all classes, and one for interface methods specific to the class. The protected section of a class contains the member data for the class. Class users cannot access members and methods in the protected section, but methods of the class can. Derived classes can access members and methods of the parent class' protected section. The private section of a class contains methods which derived classes should not have access. Although the private section of parent classes is inherited by child classes, the child class can't use the methods or access the data inherited from the private section.

A good example of the use of these access restrictions to provide a consistent interface to the user can be found in the algorithm class Correlation: For a more detailed explanation of access restrictions see Lippman and Lajoie, Sections 13.1.3.

Constants:

The first public section of a typical class is devoted to constants required by our environment. Any data object declaration preceded by the const keyword is a constant. Constants cannot be modified after they are declared, so they must be initialized in their declarations. This is typically done in the header file (see Correlation) for integral types: and in the file containing the implementation of a default constructor for objects (see auto_cstr_00.cc for a detailed example).

There are two important reason for using such static constants. First, static constants occur once in memory for every instance of their class, so if you declare fifty objects, they all share the same copy of CLASS_NAME. Secondly, declaration within a class avoids namespace conflicts. The constant is referenced outside the class as Class::CLASS_NAME (for example, Autocorrelation::CLASS_NAME). Other classes using a constant similarly named CLASS_NAME do not cause a conflict. This latter feature, though seemingly mundane, is one of the truly useful features of C++, and avoids a very common problem with namespaces (everyone in signal processing wants to use the name signal).

For more information on constants, see Lippman and Lajoie, Section 3.5. For more information on static constants, see Lippman and Lajoie, Section 13.5.

Enumeration:

When you review the public constants sections of our classes, you are sure to run across some enumerated types. At first glance you may not agree that enumerations are constants, but our philosophy is that objects of an enumerated type are guaranteed to have a value within the specified range. Let's consider a simple example (see Correlation): In this example, there are two possible values: FACTORED and UNFACTORED. FACTORED is assigned the numeric value of 0. DEF_ALG is also defined so programmers can declare a default value for this enumeration in a consistent way.

Objects of these enumerated types must have one of the values specified in the list that follows: The compiler will flag an error if enumerated objects have any value outside of their specified ranges. The first statement above is correct and neatly protects the namespace for the variable. The second line will produce a compilation error. Catching such things at compilation time greatly streamlines the software development process.

For more information on enumerated types, see Lippman and Lajoie, Section 3.8.

Default Values:

One of the methods you will find in every class is the constructor. The constructor has the same name as its class, but it is not found in a standard section of our class definition. In general you can find it with required public methods. Occasionally, constructors also appear in the class-specific public methods. The constructor performs the task of initializing an object when it is declared. In some cases, a user does not wish to pass all of the arguments the constructor requires. To handle these cases, we provide the constructor with default arguments.

The default constructor from the class Correlation is shown below: DEF_ORDER is the default argument for method, and is used whenever an Autocorrelation declaration looks like: Any method can incorporate default values for arguments. They are quite useful for preventing the need to implement numerous versions of the same method. For example, these methods, taken from CircularDelayLine, demonstrate different ways default arguments can be used: For more information on constructors with default arguments, see Lippman and Lajoie, Section 7.3.5.

Function Prototypes:

You have seen many functions prototypes already in the course of this tutorial, and as C++ function prototypes don't differ much from ANSI C function prototypes, we won't get into the details of your run-of-the-mill prototype. We do, however, want to give you some background in the more esoteric function prototype keywords and what they mean. You've already seen these keywords applied to constants; they are const and static.

Declaring a function as const ensures that the function can't change the value of the object that calls it. Several of our required public methods are declared const: You may notice that some of these methods have arguments that contain the const keyword as well. When a function argument is declared as const, the value of that argument is guaranteed not to change just as if the argument were a constant declared in the function's body. The object being passed as const need not be a constant, nor will it be a constant outside of the function to which it was passed as a constant.

The other keyword you'll encounter, static, causes a function's visibility to be the same as a static constant. Static functions have one feature that static objects do not. A static method can be called without instantiating the class that contains it. We see this used often with the operating system methods from Integral such as: Calls to these functions have the following syntax: You will see the call above in every main program. An equally important method that uses this feature is the diagnose method - which all classes are required to have in our programming paradigm.

For more information on constant functions, see Lippman and Lajoie, Section 13.3.5. For more information on static functions, see Lippman and Lajoie, Section 13.5.1.