Previous Section
 < Free Open Study > 
Next Section


2.5 Constructs for Program Verification

Chapter 1 described methods for verifying software correctness in general. In this section we look at two constructs provided in C++ to help ensure quality software (if we use them!). The first is the exception mechanism, mentioned briefly in Chapter 1. The second is the namespace mechanism, which helps handle the problem of duplicate names appearing in large programs.

Exceptions

Most programs, and student programs in particular, are written under the most optimistic of assumptions: They will compile the first time and execute properly. Such an assumption is far too optimistic for almost any program beyond "Hello world." Programs must deal with all sorts of error conditions and exceptional situations-some caused by hardware problems, some caused by bad input, and some caused by undiscovered bugs.

Aborting the program in the wake of such errors is not an option, as the user would lose all work performed since the last save. At a minimum, a program must warn the user, allow saving, and exit gracefully. In each situation, even if the exception management technique calls for terminating the program, the code detecting the error cannot know how to shut down the program, let alone shut it down gracefully. Transfer of the thread of control and information to a handler that knows how to manage the error situation is essential.

As we said in Chapter 1, working with exceptions begins at the design phase, where the unusual situations and possible error conditions are specified and decisions are made about handling each one. Now let's look at the try-catch and throw statements, which allow us to alert the system to an exception (throw the exception), to detect an exception (try code with a possible exception), and to handle an exception (catch the exception).

try-catch and throw Statements The code segment in which an exception might occur is enclosed within the try clause of a try-catch statement. If the exception occurs, the reserved word throw is used to alert the system that control should pass to the exception handler. The exception handler is the code segment in the catch clause of the associated try-catch statement, which takes care of the situation.

The code that may throw an exception is placed in a try block followed immediately by one or more catch blocks. A catch block consists of the keyword catch followed by an exception declaration, which is in turn followed by a block of code. If an exception is thrown, execution immediately transfers from the try block to the catch block whose type matches the type of the thrown exception. The exception variable receives the exception of the type that is thrown in the try block. In this way, the thrown exception communicates information about the event to the handler.

try
{
  // Code that may raise an exception and/or set some condition
  if (condition)
    throw exception_name; // frequently a string
}
catch (typename variable)
{
  // Code that handles the exception
  //  It may call a cleanup routine and exit if the
  //  program must be terminated
}
// Code to continue processing
//  This will be executed unless the catch block
//  stops the processing.

Let's look at a concrete example. You are reading and summing positive values from a file. An exception occurs if a negative value is encountered. If this event happens, you want to write a message to the screen and stop the program.

try
{
  infile >> value;
  do
  {
    if (value < 0)
      throw string("Negative value"); // Exception is a string
    sum = sum + value;
  } while (infile);
}
catch (string message)
// Parameter of the catch is type string
{
   // Code that handles the exception
   cout << message << " found in file. Program aborted."
   return 1;
}
// Code to continue processing if exception not thrown
cout << "Sum of values on the file: " << sum;

If value is less than zero, a string is thrown; catch has a string as a parameter. The system recognizes the appropriate handler by the type of the exception and the type of the catch parameter. We will deal with exceptions throughout the rest of the book. As we do, we will show more complex uses of the try-catch statement.

Here are the rules we follow in using exceptions within the context of the ADTs we develop:

  • The preconditions/postconditions on the functions represent a contract between the client and the ADT that defines the exception(s) and specifies who (client or ADT) is responsible for detecting the exception(s).

  • The client is always responsible for handling the exception.

  • The ADT code does not check the preconditions.

When we design an ADT, the software that uses it is called the client of the class. In our discussion, we use the terms client and user interchangeably, thinking of them as the people writing the software that uses the class, rather than the software itself.

Standard Library Exceptions In C++, the run-time environment (for example, a divide-by-zero error) may implicitly generate exceptions in addition to those being thrown explicitly by the program. The standard C++ libraries provide a predefined hierarchy of error classes in the standard header file, stdexcept, including

  • logic_error

  • domain_error

  • invalid_argument

  • length_error

  • out_of_range

In addition, the class runtime_error provides the following error classes:

  • range error

  • overflow_error

  • underflow error

Namespaces

Different code authors tend to use many of the same identifier names (name, for example). Because library authors often have the same tendency, some of these names may appear in the library object code. If these libraries are used simultaneously, name clashes may occur, as C++ has a "one definition rule," which specifies that each name in a C++ program should be defined exactly once. The effect of dumping many names into the namespace is called namespace pollution.

The solution to namespace pollution involves the use of the namespace mechanism. A namespace is a C++ language technique for grouping a collection of names that logically belong together. This facility allows the enclosing of names in a scope that is similar to a class scope; unlike class scope, however, a namespace can extend over several files and be broken into pieces in a particular file.

Creating a Namespace A namespace is declared with the keyword namespace before the block that encloses all of the names to be declared within the space. To access variables and functions within a namespace, place the scope resolution operator (: :) between the namespace name and the variable or function name. For example, if we define two namespaces with the same function,

namespace myNames
{
  void getData(int&);
};

namespace yourNames
{
  void getData(int&);
};

you can access the functions as follows:

myNames::getData(int& dataValue);   // getData from myNames
yourNames::getData(int& dataValue); // getData from yourNames

This mechanism allows identical names to coexist in the same program.

Access to Identifiers in a Namespace Access to names declared in a namespace may always be obtained by explicitly qualifying the name with the name of the namespace with the scope resolution operator (::), as shown in the last example. Explicit qualification of names is sufficient to use these names. However, if many names will be used from a particular namespace or if one name is used frequently, then repeated qualification can be awkward. A using declaration avoids this repetition for a particular identifier. The using declaration creates a local alias for the qualified name within the block in which it is declared, making qualification unnecessary.

For example, by putting the following declaration in the program,

using myNames::getData;

the function getData can be used subsequently without qualification. To access getData in the namespace yourNames, it would have to be qualified: yourNames::getData.

One other method of access should be familiar: a using directive. For example,

using namespace myNames;

provides access to all identifiers within the namespace myNames. You undoubtedly have employed a using directive in your program to access cout and cin defined in the namespace std without having to qualify them.

A using directive does not bring the names into a given scope; rather, it causes the name-lookup mechanism to consider the additional namespace specified by the directive. The using directive is easier to use, but it makes a large number of names visible in this scope and can lead to name clashes.

Rules for Using the Namespace std Here are the rules that we follow in the balance of the text in terms of qualifying identifiers from the namespace std:

  • In function prototypes and/or function definitions, we qualify the identifier in the heading.

  • In a function block, if a name is used once, it is qualified. If a name is used more than once, we use a using declaration with the name.

  • If two or more names are used from a namespace, we use a using directive.

  • A using directive is never used outside a class or a function block.

The goal is to never pollute the global namespace more than is necessary.

C++: Character and String Library Functions
Start example

C++ provides many character and string-handling facilities in its standard library. Operations available through <cctype> include testing to see whether a character is a letter, number, control character, uppercase, or lowercase. Operations available through <cstring> include concatenating two strings, comparing two strings, and copying one string into another. See Appendix C for more details.

End example


Previous Section
 < Free Open Study > 
Next Section
Converted from CHM to HTML with chm2web Pro 2.85 (unicode)