Previous Table of Contents Next


7.2.9.8. Exceptions

A program that discovers a state of affairs with which it cannot cope may wish to give up and transfer control elsewhere. In principle, the program that found the problem does not need to know what is going to happen next, as long as it can say in enough detail what is wrong so as to allow some other part of the program to handle the problem.

Exceptions are the mechanism by which C++ handles such situations. A program indicates that it cannot continue by saying

   throw expression;

Executing a throw statement either terminates the program or transfers control to a try statement whose execution is in progress, and that has allowed for the possibility of catching a value of the same type (or a type that is similar enough). If no appropriate try statement is presently being executed, the program terminates.

For example,

   try {
         throw 123;
   } catch (char c) {
         cout << “caught a character “ << c << endl;
   } catch (int n) {
         cout << “caught an integer “ << n << endl;
   }

prints

   caught an integer 123

and then continues execution from after the last }. On the other hand, changing 123 to 123.0 would cause the program to terminate unless some other try statement was presently executing that can catch values of type double.

Exceptions are often class objects, and are usually thrown in one function and caught in another; so we will return to the discussion of exceptions after we have explored functions and classes.

7.2.9.9. Overloading

More than one function can have the same name, provided that the functions differ in type or number of parameters. Such a function is called overloaded. Calling an overloaded function implies a compile-time check: Is there a single function that is a better match for the arguments than any other? To be the best match, a function must be a better match than the others on one or more arguments and no worse in any argument. The notion of “better match” is somewhat complicated to describe precisely, but intuitively speaking, the less conversion that has to be done to get the argument to the type of the parameter, the better the match. So, for example, we can overload abs:

   int abs(int x) { return x<0? -x: x; }
   short abs(short x) { return x<0? -x: x; }
   long abs(long x) { return x<0? -x: x; }
   float abs(float x) { return x<0? -x: x; }
   double abs(double x) { return x<0? -x: x; }

after which abs(0) will call the int version, abs(-2.3) will call the double version, and abs(‘c’) will call the int version because converting a char to int is preferable to converting it to any other integral type.

7.2.9.10. Operators

Functions can have names such as operator+ (or, equivalently, operator +); such functions define additional meanings for the operator symbols. To stop programmers from changing the well-established core language, C++ requires such functions to have at least one parameter of user-defined type. Moreover, programmers cannot invent new operator symbols; they are limited to the ones that are already there.

For example, if s is a variable of type string, and we write

   cout << s

that expression is really equivalent to calling

   operator<<(cout, s)

and we can define the function thus called as

   ostream& operator<<(ostream& stream, const string& s)
   {
        // ...
   }

Two operators with the unusual names of operator[] and operator() figure prominently in C++ programs. They permit the definition of syntactic forms that would otherwise denote subscripting and function calls. So, if x has a user-defined type,

   x(arg1, arg2, arg3)

is an abbreviation for

   x.operator() (arg1, arg2, arg3)

and

  x[arg]

is an abbreviation for

   x.operator[] (arg)

These operators allow for user-defined types that look like arrays and functions.

7.2.10. Namespaces

We have seen program examples that define names like abs and sqrt, which are also part of the standard library. How can we avoid clashing with that library? C++ uses namespaces to answer this question.

A namespace is purely a syntactic notion: By enclosing one or more declarations in

   namespace name { declarations }

the programmer can “attach” the given name to each name declared in the declarations. Such names are then considered different from the same names in other namespaces.

If x is defined in namespace N, the most direct way to refer to that x from outside N is as N::x. From inside N, no special pleading is necessary. For example:

   namespace Mine {
         int x;

         void f()       // Mine::f
         {
               x = 0;   // Mine::x
         }
   }

   namespace Yours {
         int x;

         void f()       // Yours::f
         {
              x = 0;    // Yours::x
         }
   }

   int x;

   void f()
   {
         x = 0;         // global x
         Mine::x = 0;
         Yours::x = 0;
         Mine::f();
         Yours::f();
   }

Alternatively, it is possible to make the contents of a namespace available in the current context in one of two ways. The safer of the two is to mention a single name:

   using Mine::x;

says that x should refer to Mine::x within the current scope. Alternatively, one can obtain wholesale access to a namespace by writing

   using namespace Yours;

after which all names from Yours are available without further ado.

Of course, making names available in quantity can give rise to ambiguity, so that after seeing the definitions of Mine and Yours above, saying

   int x;

   void g()
   {
         using namespace Mine;

         x = 0;       // ambiguous
   }

would fail because the compiler would not know whether to use Mine::x or the global x.

The standard library puts all its names in a namespace called std, to avoid clashes with user-defined names. It is therefore not uncommon for programs to begin by saying

   using namespace std;

This strategy has the additional advantage that vendors can create alternative versions of the standard library, which can then be accessed by changing the name of a namespace in a single place only.


Previous Table of Contents Next