Chapter 30

Power-User C++ Features


CONTENTS

C++ is an evolving language, and, as such, it frequently undergoes review and improvement. New power features that have been added to C++ in the recent past are exceptions, templates, Run-Time Type Information (RTTI), and namespace support. These are programming issues that will become more and more important to Windows developers. For that reason, learning to implement these new features into your programming projects is a must-do task.

Understanding Exceptions

When you write applications using Visual C++, sooner or later you're going to run into exceptions. An exception is a special type of error object that is created when something goes wrong in a program. After Visual C++ creates the exception object, it sends it to your program, an action called throwing an exception. It's up to your program to catch the exception. You do this by writing the exception-handling code. In this section, you get the inside info on these important error-handling objects.

Simple Exception Handling

The mechanism used by exception-handling code is really pretty simple. You place the source code that you want guarded against errors inside a try block. You then construct a catch program block that acts as the error handler. If the code in the try block (or any code called from the try block) generates an exception (called throwing an exception, remember), the try block immediately ceases execution and the program continues inside the catch block.

For example, memory allocation is one place in a program where you might expect to run into trouble. Listing 30.1 shows a nonsensical little program that allocates some memory and then immediately deletes it. Because memory allocation could fail, the code that allocates the memory is enclosed in a try program block. If the pointer returned from the memory allocation is NULL, the try block throws an exception. In this case, the exception object is a string.


Listing 30.1  EXCEPTION.CPP-Simple Exception Handling

#include <iostream.h>



int main()

{

    int* buffer;

    char* msg[] = {"Memory allocation failed!"};

    

    try

    {

        buffer = new int[256];



        if (buffer == NULL)

            throw *msg;

        else

            delete buffer;

    }

    catch(char* exception)

    {

        cout << exception << endl;

    }



    return 0;

}


When the program throws the exception, program execution jumps to the first line of the catch program block. In the case of Listing 30.1, this line just prints out a message, after which the function's return line is executed and the program ends.

If the memory allocation is successful, the program executes the entire try block, deleting the buffer. Then program execution skips over the catch block completely, in this case going directly to the return statement.

NOTE
The catch program block does more than direct program execution. It actually catches the exception object thrown by the program. For example, in Listing 30.1, you can see the exception object being caught inside the parentheses following the catch keyword. This is very similar to a parameter being received by a method. In this case, the type of the "parameter" is char* and the name of the parameter is exception.

Exception Objects

The beauty of C++ exceptions is that the exception object thrown can be just about any kind of data structure you like. For example, you might want to create an exception class for certain kinds of exceptions that occur in your programs. Listing 30.2 shows a program that defines a general-purpose exception class called CException. In the case of a memory-allocation failure, the main program creates an object of the class and throws it. The catch block catches the CException object, calls the object's GetError() member function to get the object's error string, and then displays the string on the screen.


Listing 30.2  EXCEPTION2.CPP-Creating an Exception Class

#include <iostream.h>



class CException

{

protected:

    char* m_msg;



public:

    CException(char *msg)

    {

        m_msg = msg;

    };



    ~CException(){};



    char* GetError()

    {

        return m_msg;

    };

};



int main()

{

    int* buffer;

    

    try

    {

        buffer = new int[256];



        if (buffer == NULL)

        {

            CException* exception =

                new CException("Memory allocation failed!");

            throw exception;

        }

        else

            delete buffer;

    }

    catch(CException* exception)

    {

        char* msg = exception->GetError();

        cout << msg << endl;

    }



    return 0;

}


An exception object can be as simple as an integer error code or as complex as a fully developed class. Whatever works best for your program is the way to go. You might, for example, want to derive specific types of exception classes from the general exception class developed in Listing 30.2.

Placing the catch Block

The catch program block doesn't have to be in the same function as the one in which the exception is thrown. When an exception is thrown, the system starts "unwinding the stack," looking for the nearest catch block. If the catch block is not found in the function that threw the exception, the system looks in the function that called the throwing function. This search continues on up the function-call stack. If the exception is never caught, the program halts.

Listing 30.3 is a short program that demonstrates this concept. The program throws the exception from the AllocateBuffer() function but catches the exception in main(), which is the function from which AllocateBuffer() is called.


Listing 30.3  EXCEPTION3.CPP-Catching Exceptions Outside of the Throwing Function

#include <iostream.h>



class CException

{

protected:

    char* m_msg;



public:

    CException(char *msg)

    {

        m_msg = msg;

    };



    ~CException(){};



    char* GetError()

    {

        return m_msg;

    };

};



int* AllocateBuffer()

{

    int* buffer = new int[256];



    if (buffer == NULL)

    {

        CException* exception =

            new CException("Memory allocation failed!");

        throw exception;

    }



    return buffer;

}



int main()

{

    int* buffer;

    

    try

    {

        buffer = AllocateBuffer();

        delete buffer;

    }

    catch(CException* exception)

    {

        char* msg = exception->GetError();

        cout << msg << endl;

    }



    return 0;

}


Handling Multiple Types of Exceptions

Because it's often the case that a block of code generates more than one type of exception, you can use multiple catch blocks with a try block. You might, for example, need to be on the lookout for both CException and char* exceptions. Because a catch block must receive a specific type of exception object (except in a special case that you'll learn about soon), you need two different catch blocks to watch for both CException and char* exception objects.

The special case I referred to in the previous paragraph is a catch block that can receive any type of exception object. You define this type of catch block by placing ellipses in the parentheses, rather than a specific argument. The problem with this sort of multipurpose catch block is that you have no access to the exception object received and so must handle the exception in some general way.

Listing 30.4 is a program that generates three different types of exceptions based on a user's input. When you run the program, you're instructed to enter a value between 4 and 8, except for 6. If you enter a value less than 4, the program throws a CException exception; if you enter a value greater than 8, the program throws a char* exception; and, finally, if you happen to enter 6, the program throws the entered value as an exception.

Although the program throws the exceptions in the GetValue() function, the program catches them all in main(). The try block in main() is associated with three catch blocks. The first catches the CException object, the second catches the char* object, and the third catches any other exception that happens to come down the pike.

NOTE
Just as with if-else statements, the order in which you place catch program blocks can have a profound effect on program execution. You should always place the most specific catch blocks first. For example, in Listing 30.4, if the catch(...) block was first, none of the other catch blocks would ever be called. This is because the catch(...) is as general as you can get, catching every single exception that the program throws. In this case (as in most cases), you want to use catch(...) to receive only the leftover exceptions.


Listing 30.4  EXCEPTION4.CPP-Using Multiple catch Blocks

#include <iostream.h>



class CException

{

protected:

    char* m_msg;



public:

    CException(char *msg)

    {

        m_msg = msg;

    };



    ~CException(){};



    char* GetError()

    {

        return m_msg;

    };

};



int GetValue()

{

    int value;



    cout << "Type a number from 4 to 8 (except 6):" << endl;

    cin >> value;



    if (value < 4)

    {

        CException* exception =

            new CException("Value less than 4!");

        throw exception;

    }

    else if (value > 8)

    {

        throw "Value greater than 8!";

    }

    else if (value == 6)

    {

        throw value;

    }

    return value;

}



int main()

{

    try

    {

        int value = GetValue();

        cout << "The value you entered is okay." << endl;

    }

    catch(CException* exception)

    {

        char* msg = exception->GetError();

        cout << msg << endl;

    }

    catch(char* msg)

    {

        cout << msg << endl;

    }

    catch(...)

    {

        cout << "Caught unknown exception!" << endl;

    }



    return 0;

}


NOTE
When your program catches an exception, it has several courses of action at its disposal. It can handle the exception, it can rethrow the exception for handling somewhere else in the program, or it can both handle the exception and rethrow it. To rethrow an exception, use the throw keyword with no argument.

Exploring Templates

It's my guess that, at one time or another, you wished you could develop a single function or class that could handle any kind of data. Sure, you can use function overloading to write several versions of a function, or you can use inheritance to derive several different classes from a base class. But, in these cases, you still end up writing many different functions or classes. If only there was a way to make functions and classes a little smarter, so that you could write just one that handled any kind of data you wanted to throw at it. Guess what? There is a way to accomplish this seemingly impossible task. You need to use something called templates, which are the focus of this section.

Introducing Templates

A template is a kind of blueprint for a function or class. You write the template in a general way, supplying placeholders, called parameters, for the data objects that the final function or class will manipulate. A template always begins with the keyword template followed by a list of parameters between angle brackets, like this:


template<class Type>

You can have as many parameters as you need, and you can name them whatever you like, but each must begin with the class keyword (in this case, class doesn't refer to a C++ class) and must be separated by commas, like this:


template<class Type1, class Type2, class Type3>

As you may have guessed from the previous discussion, there are two types of templates: function and class. The following sections describe how to create and use both types of templates.

Creating Function Templates

A function template starts with the template line you just learned about, followed by the function's declaration, as shown in Listing 30.5. The template line specifies the types of arguments that will be used when calling the function, whereas the function's declaration specifies how those arguments are to be received as parameters by the function. Every parameter specified in the template line must be used by the function declaration. Notice the Type1 immediately before the function name. Type1 is a placeholder for the function's return type, which will vary depending upon how the template is used.


Listing 30.5  LST30_05.TXT-The Basic Form of a Function Template

template<class Type1, class Type2>

Type1 MyFunction(Type1 data1, Type1 data2, Type2 data3)

{

    // Place the body of the function here.

}


An actual working example will help you understand how function templates become functions. A common example is a Min() function that can accept any type of arguments. Listing 30.6 is a short program that defines a template for a Min() function and then uses that function in main(). When you run the program, the program displays the smallest value of whatever data is sent as arguments to Min(). This works because the compiler takes the template and creates functions for each of the data types that are compared in the program.


Listing 30.6  TEMPLATE.CPP-Using a Typical Function Template

#include <iostream.h>



template<class Type>

Type Min(Type arg1, Type arg2)

{

    Type min;



    if (arg1 < arg2)

        min = arg1;

    else

        min = arg2;



    return min;

}



int main()

{

    cout << Min(15, 25) << endl;

    cout << Min(254.78, 12.983) << endl;

    cout << Min('A', 'Z') << endl;



    return 0;

}


NOTE
Notice how, in Listing 30.6, the Min() template uses the data type Type not only in its parameter list and function argument list, but also in the body of the function, in order to declare a local variable. This illustrates how you can use the parameter types just as you would use any specific data type such as int or char.

Because function templates are so flexible, they can often lead to trouble. For example, in the Min() template, you have to be sure that the data types that you supply as parameters can be compared. If you tried to compare two classes, your program would not compile unless the classes overloaded the < and > operators.

Another way you can run into trouble is when the arguments you supply to the template are not used as you think. For example, what if you added the following line to main() in Listing 30.6?


cout << Min('APPLE', 'ORANGE') << endl;

If you don't think about what you're doing in the previous line, you may jump to the conclusion that the returned result will be APPLE. The truth is, however, that the preceding line may or may not give you the result you expect. Why? Because the "APPLE" and "ORANGE" string constants result in pointers to char. This means that the program will compile fine, with the compiler creating a version of Min() that compares char pointers. But there's a big difference between comparing two pointers and comparing the data to which the pointers point. If "ORANGE" happens to be stored at a lower address than "APPLE", the preceding call to Min() results in "ORANGE".

A way to avoid this problem is to provide a specific replacement function for Min() that defines exactly how you want the two string constants compared. When you provide a specific function, the compiler uses that function instead of creating one from the template. Listing 30.7 is a short program that demonstrates this important technique. When the program needs to compare the two strings, it doesn't call a function created from the template but instead uses the specific replacement function.


Listing 30.7  TEMPLATE2.CPP-Using a Specific Replacement Function

#include <iostream.h>

#include <string.h>



template<class Type>

Type Min(Type arg1, Type arg2)

{

    Type min;



    if (arg1 < arg2)

        min = arg1;

    else

        min = arg2;



    return min;

}



char* Min(char* arg1, char* arg2)

{

    char* min;



    int result = strcmp(arg1, arg2);



    if (result < 0)

        min = arg1;

    else

        min = arg2;



    return min;

}



int main()

{

    cout << Min(15, 25) << endl;

    cout << Min(254.78, 12.983) << endl;

    cout << Min('A', 'Z') << endl;

    cout << Min("APPLE", "ORANGE") << endl;



    return 0;

}


Creating Class Templates

Just as you can create abstract functions with function templates, so too can you create abstract classes with class templates. A class template represents a class, which in turn represents an object. When you define a class template, the compiler takes the template and creates a class. You then instantiate objects of the class. As you can see, class templates add another layer of abstraction to the concept of classes.

You define a class template much as you define a function template, by supplying the template line followed by the class's declaration, as shown in Listing 30.8. Notice that, just as with a function template, you use the abstract data types given as parameters in the template line in the body of the class in order to define member variables, return types, and other data objects.


Listing 30.8  LST30_08.TX-Defining a Class Template

template<class Type>

class CMyClass

{

protected:

    Type arg;



public:

    CMyClass(Type arg)

    {

        CMyClass::arg = arg;

    }



    ~CMyClass() {};

};


When you're ready to instantiate objects from the template class, you must supply the data type that'll replace the template parameters. For example, to create an object of the CMyClass class, you might use a line like this:


CMyClass<int> myClass(15);

The previous line creates a CMyClass object that uses integers in place of the abstract data type. If you wanted the class to deal with floating-point values, you'd create an object of the class something like this:


CMyClass<float> myClass(15.75);

For a more complete example, suppose that you want to create a class that stores two values and that has member functions that compare those values. Listing 30.9 is a program that does just that. First, the listing defines a class template called CCompare. This class stores two values that are supplied to the constructor. The class also includes the usual constructor and destructor, as well as member functions for determining the larger or smaller of the values, or to determine whether the values are equal.


Listing 30.9  TEMPLATE3.CPP-Using a Class Template

#include <iostream.h>



template<class Type>

class CCompare

{

protected:

    Type arg1;

    Type arg2;



public:

    CCompare(Type arg1, Type arg2)

    {

        CCompare::arg1 = arg1;

        CCompare::arg2 = arg2;

    }



    ~CCompare() {}



    Type GetMin()

    {

        Type min;



        if (arg1 < arg2)

            min = arg1;

        else

            min = arg2;



        return min;

    }



    Type GetMax()

    {

        Type max;



        if (arg1 > arg2)

            max = arg1;

        else

            max = arg2;



        return max;

    }



    int Equal()

    {

        int equal;



        if (arg1 == arg2)

            equal = 1;

        else

            equal = 0;



        return equal;

    }

};



int main()

{

    CCompare<int> compare1(15, 25);

    CCompare<double> compare2(254.78, 12.983);

    CCompare<char> compare3('A', 'Z');



    cout << "THE COMPARE1 OBJECT" << endl;

    cout << "Lowest: " << compare1.GetMin() << endl;

    cout << "Highest: " << compare1.GetMax() << endl;

    cout << "Equal: " << compare1.Equal() << endl;

    cout << endl;



    cout << "THE COMPARE2 OBJECT" << endl;

    cout << "Lowest: " << compare2.GetMin() << endl;

    cout << "Highest: " << compare2.GetMax() << endl;

    cout << "Equal: " << compare2.Equal() << endl;

    cout << endl;



    cout << "THE COMPARE2 OBJECT" << endl;

    cout << "Lowest: " << compare3.GetMin() << endl;

    cout << "Highest: " << compare3.GetMax() << endl;

    cout << "Equal: " << compare3.Equal() << endl;

    cout << endl;



    return 0;

}


The main program instantiates three objects from the class template, one that deals with integers, one that uses floating-point values, and one that stores and compares character values. After creating the three CCompare objects, main() calls the objects' member functions in order to display information about the data stored in each object. Figure 30.1 shows the program's output.

Figure 30.1 : The Template3 program creates three different objects from a class template.

You can, of course, pass as many parameters as you like to a class template, just as you can with a function template. Listing 30.10 shows a class template that uses two different types of data.


Listing 30.10   LST30_10.TXT-Using Multiple Parameters with a Class Template

template<class Type1, class Type2>

class CMyClass

{

protected:

    Type1 arg1;

    Type2 arg2;



public:

    CMyClass(Type1 arg1, Type2 arg2)

    {

        CMyClass::arg1 = arg1;

        CMyClass::arg2 = arg2;

    }



    ~CMyClass() {}

};


To instantiate an object of the CMyClass class, you might use a line like this:


CMyClass<int, char> myClass(15, 'A');

Finally, you can use specific data types, as well as the placeholder data types, as parameters in a class template. You just add the specific data type to the parameter list, just as you add any other parameter. Listing 30.11 is a short program that creates an object from a class template that uses two abstract parameters and one specific data type.


Listing 30.11  TEMPLATE4.CPP-Using Specific Data Types as Parameters
in a Class Template

#include <iostream.h>



template<class Type1, class Type2, int num>

class CMyClass

{

protected:

    Type1 arg1;

    Type2 arg2;

    int num;



public:

    CMyClass(Type1 arg1, Type2 arg2, int num)

    {

        CMyClass::arg1 = arg1;

        CMyClass::arg2 = arg2;

        CMyClass::num = num;

    }



    ~CMyClass() {}

};



int main()

{

    CMyClass<int, char, 0> myClass(15, 'A', 10);



    return 0;

}


Using Run-Time Type Information

Run-Time Type Information (RTTI) was added to C++ so that programmers could obtain information about objects at runtime. This ability is especially useful when you're dealing with polymorphic objects, because it enables your program to determine at runtime what exact type of object it's currently working with. Later in this section, you'll see how important this type of information can be when you're working with class hierarchies. RTTI can also be used to safely downcast an object pointer. In this section, you'll discover how RTTI works and why you'd want to use it.

Introducing RTTI

The RTTI standard introduces three new elements to the C++ language. The dynamic_cast operator performs downcasting of polymorphic objects; the typeid operator retrieves information (in the form of a type_info object) about an object; and the type_info class stores information about an object, providing member functions that can be used to extract that information.

The public portion of the type_info class is defined in Visual C++ as shown in Listing 30.12.


Listing 30.12  LST30_12.TXT-The type_info Class

class type_info {

public:

     virtual ~type_info();

     int operator==(const type_info& rhs) const;

     int operator!=(const type_info& rhs) const;

     int before(const type_info& rhs) const;

     const char* name() const;

     const char* raw_name() const;

};


As you can see, the class provides member functions that can compare objects for equality, as well as return the object's name, both as a readable text string and as a raw decorated object name. The before() member function remains a bit mysterious and poorly documented. According to Microsoft, the Visual C++ implementation of before() is used "to determine the collating sequence of types." Microsoft further states that "there is no link between the collating order of types and inheritance relationships."

Performing Safe Downcasts

Once you start writing a lot of OOP programs, you'll run into times when you need to downcast one type of object to another. Downcasting is the act of converting a base-class pointer to a derived-class pointer (a derived class being a class that's derived from the base class). You use dynamic_cast to downcast an object, like this:


Type* ptr = dynamic_cast<Type*>(Pointer);

In the preceding example, Type is the type to which the object should be cast, and Pointer is a pointer to the object. If the pointer cannot be safely downcast, the dynamic_cast operator returns 0.

Suppose, for example, that you have a base class called CBase and a class derived from CBase called CDerived. Because you want to take advantage of polymorphism, you obtained a pointer to CDerived, like this:


CBase* derived = new CDerived;

Notice that, although you're creating a CDerived object, the pointer is of the base-class type, CBase. This is a typical scenario in programs that take advantage of OOP polymorphism.

Now suppose that you want to safely downcast the CBase pointer to a CDerived pointer. You might use dynamic_cast, as follows:


CDerived* ptr = dynamic_cast<CDerived*>(derived);

If ptr gets a value of 0, the downcast was not allowed.

Getting Object Information

As I mentioned previously, you can use the typeid operator to obtain information about an object. Although the dynamic_cast operator applies only to polymorphic objects, you can use typeid on any type of data object. For example, to get information about the int data object, you could use lines like these:


const type_info& ti = typeid(int);

cout << ti.name();

In the first line, you can see that the typeid operator returns a reference to a type_info object. You can then use the object's member functions to extract information about the data object. In the preceding example, the cout object will output the word int. The typeid operator's single argument is the name of the data object for which you want a type_info object.

Of course, a better use for typeid is to compare and get information about classes that you have defined in your program. Listing 30.13 is a short program that prints out information about the classes it defines.


Listing 30.13  RTTI.CPP-Using the typeid Operator

#include <iostream.h>

#include <typeinfo.h>



class CBase

{

public:

    CBase() {};

    ~CBase() {};

};



class CDerived : public CBase

{

public:

    CDerived() {};

    ~CDerived() {};

};



int main()

{

    CBase* base = new CBase;

    CBase* derived = new CDerived;



    const type_info& ti1 = typeid(CBase);

    const type_info& ti2 = typeid(CDerived);



    cout << "First object's name: " << ti1.name() << endl;

    cout << "First object's raw name: " << ti1.raw_name() << endl;

    cout << endl;



    cout << "Second object's name: " << ti2.name() << endl;

    cout << "Second object's raw name: " << ti2.raw_name() << endl;

    cout << endl;



    if (ti1 == ti2)

        cout << "The two objects are equal." << endl;

    else

        cout << "The two objects are not equal." << endl;



    cout << endl;



    delete base;

    delete derived;



    return 0;

}


Listing 30.13 first defines a base class called CBase. The program then derives a second class, called CDerived, from the base class. In main(), the program instantiates an object from each class, the objects being called base and derived. Then the program calls typeid to obtain type_info objects for each class. Finally, the program calls the type_info member functions to extract information about the classes, as well as to compare the classes for equality. Figure 30.2 shows the program's output. If you have trouble running the RTTI program shown in Listing 30.13, jump ahead to the next section, which tells you how to enable RTTI.

Figure 30.2 : This is Listing 30.13 in action.

Preparing to Use RTTI

If you got a strange error message or warning when you tried to compile the RTTI program, you probably don't yet have RTTI enabled. To enable RTTI, follow the procedure below:

  1. Select the Build, Settings command from Developer Studio's menu bar. The Project Settings dialog box appears (Figure 30.3).
    Figure 30.3 : The Project Settings dialog box.

  2. Click the C/C++ tab. The C/C++ setting-options page appears, as shown in Figure 30.4.
    Figure 30.4 : The C/C++ options page.

  3. In the Categor_y box, select the C++ Language item. The C++ language options appear (Figure 30.5).
    Figure 30.5 : The C++ language options.

  4. Select the Enable Run-Time Type Information (RTTI) option and then click OK to finalize your choices.

Also, be sure that you include the TYPEID.H header file in any source-code file that calls the typeid operator. If you fail to do this, your program will not compile.

A Common Use for RTTI

All of the previous discussion is okay from a nuts-and-bolts perspective, but why would you want to use RTTI in the first place? When you're writing programs that incorporate polymorphic objects, RTTI comes in handy. To see why, first take a look at Listing 30.14, which is a program that uses a simple case of polymorphism. The program defines two classes. The CBase class acts as the base class for the second class, CDerived. That is, CDerived is derived from CBase. In addition, CDerived overrides CBase's two virtual functions so that the functions perform as is appropriate for a Derived object.


Listing 30.14  RTTI2.CPP-A Typical Case of Class Derivation

#include <iostream.h>

#include <typeinfo.h>



class CBase

{

public:

    CBase() {};

    ~CBase() {};



    virtual char* Func1() { return "CBase Func1()";};

    virtual char* Func2() { return "CBase Func2()"; };

};



class CDerived : public CBase

{

public:

    CDerived() {};

    ~CDerived() {};



    char* Func1() { return "CDerived Func1()";};

    char* Func2() { return "CDerived Func2()"; };

};



int main()

{

    CBase* base = new CBase;

    CBase* derived = new CDerived;



    cout << base->Func1() << endl;

    cout << base->Func2() << endl;

    cout << endl;



    cout << derived->Func1() << endl;

    cout << derived->Func2() << endl;

    cout << endl;



    delete base;

    delete derived;



    return 0;

}


In main(), the program creates an object from each class. But notice how pointers to both objects are of the CBase type. This is how polymorphism works. Although both pointers are of the CBase type, when the pointers are used to call the polymorphic Func1() and Func2() functions, the correct versions of the functions are called for the object type. You can tell from the program output, shown in Figure 30.6, that this is true. You've just witnessed polymorphism in action. Although both pointers are of the base-class type, the program automatically knows which set of member functions to call.

Figure 30.6 : Listing 30.14's output demonstrates polymorphism in action.

Now suppose that the CBase class's Func2() member function was not virtual, but you still wanted to override the function in a derived class. As long as you're using pointers to the specific class types-that is, the derived class's pointer is of the derived class's type rather than of the base class's type-you won't have any problem overriding the function. But what if you're still using polymorphism in the program? Then, when you try to call the derived object's Func2() member function, the base class's Func2() gets called instead. That keyword virtual sure makes a big difference, eh?

The solution to this dilemma is to use RTTI casting when needed to downcast the class pointers when you need to be sure that the derived class's version of the function is called rather than the base class's. Listing 30.15 is a program that demonstrates how this works. As in Listing 30.14, this program defines the CBase and CDerived classes. However, in this case, we've removed the virtual keyword from CBase's Func2() member function. In other words, Func2() is no longer a polymorphic function.


Listing 30.15  RTTI3.CPP-Using Casting

#include <iostream.h>

#include <typeinfo.h>



class CBase

{

public:

    CBase() {};

    ~CBase() {};



    virtual char* Func1() { return "CBase Func1()";};

    char* Func2() { return "CBase Func2()"; };

};



class CDerived : public CBase

{

public:

    CDerived() {};

    ~CDerived() {};



    char* Func1() { return "CDerived Func1()";};

    char* Func2() { return "CDerived Func2()"; };

};



int main()

{

    CBase* base = new CBase;

    CBase* derived = new CDerived;



    cout << base->Func1() << endl;

    cout << base->Func2() << endl;

    cout << endl;



    cout << "BEFORE CAST:" << endl;

    cout << derived->Func1() << endl;

    cout << derived->Func2() << endl;

    cout << endl;



    CDerived* ptr = dynamic_cast<CDerived*>(derived);



    cout << "AFTER CAST: " << endl;

    cout << ptr->Func1() << endl;

    cout << ptr->Func2() << endl;

    cout << endl;



    delete base;

    delete derived;



    return 0;

}


In main(), the program instantiates objects from both the CBase and CDerived classes, with the pointers to these objects both being of the CBase type. Figure 30.7 shows the program's output. When the program tries to call the derived object's Func2() through the CBase pointer, it gets CBase's Func2(). Because the base class's Func2() is no longer virtual, polymorphism fails to operate. (Func1() is still virtual, so it performs just fine, thank you.) However, after using the dynamic_cast operator on the derived object in order to cast the pointer to the CDerived type, the call to Func2() works properly.

Figure 30.7 : This version of the program must downcast a pointer to get the results you want.

Of course, in the case of Listing 30.15, you might as well just go ahead and ignore polymorphism completely, using the specific pointer types in the first place rather than downcasting a pointer later in the program. What happens, though, when you have an array of objects that you want to process in a loop? All of the pointers in the array have to be of the same type, but you need to know when to call the nonvirtual Func2() for the derived object.

You can do this by using dynamic_cast to downcast the pointer. If the downcast is successful, you have a CBase pointer that points to a CDerived object and must call that object's specific Func2(). If, however, the downcast fails, you have a CBase pointer that points to a CBase object.

Listing 30.16 is a program that demonstrates how this process works. The program creates an array of CBase pointers, one of which actually points to an object of the CDerived class. In the first for loop, the program processes the pointer array without concern for the object that each pointer represents. As you can see in the program's output (Figure 30.8), when it comes time to call the CDerived object's Func2(), the CBase class's Func2() gets called instead. However, in the second for loop, before calling Func2(), the program attempts to cast the pointer in the array to a pointer of the CDerived type. If the cast succeeds (ptr doesn't equal 0), the program must call Func2() through the new pointer in order to ensure that the correct version of the function is called. If the cast fails, the program calls Func2() through the original pointer.

Figure 30.8 : This is a run of Listing 30.16.


Listing 30.16  RTTI4.CPP-A Final Example of RTTI Downcasting

#include <iostream.h>



class CBase

{

public:

    CBase() {};

    ~CBase() {};



    virtual char* Func1() { return "CBase Func1()";};

    char* Func2() { return "CBase Func2()"; };

};



class CDerived : public CBase

{

public:

    CDerived() {};

    ~CDerived() {};



    char* Func1() { return "CDerived Func1()";};

    char* Func2() { return "CDerived Func2()"; };

};



int main()

{

    int x;

    CBase* ptrs[3];



    ptrs[0] = new CBase;

    ptrs[1] = new CBase;

    ptrs[2] = new CDerived;



    cout << "WITHOUT RTTI DOWNCASTING:" << endl;



    for (x=0; x<3; ++x)

    {

        cout << ptrs[x]->Func1() << endl;

        cout << ptrs[x]->Func2() << endl;

        cout << endl;

    }





    cout << "WITH RTTI DOWNCASTING:" << endl;



    for (x=0; x<3; ++x)

    {

        cout << ptrs[x]->Func1() << endl;

        

        CDerived* ptr = dynamic_cast<CDerived*>(ptrs[x]);



        if (ptr)

            cout << ptr->Func2() << endl;

        else

            cout << ptrs[x]->Func2() << endl;



        cout << endl;



        delete ptrs[x];

    }





    return 0;

}


Namespaces

Every programmer is already familiar with the concept of namespaces. Basically, a namespace defines a scope in which duplicate identifiers cannot be used. For example, you already know that you can have a global variable named value and then also define a function with a local variable called value. Because the two variables are in different namespaces, your program knows that it should use the local value when inside the function and the global value everywhere else.

Namespaces, however, do not extend far enough to cover some very thorny problems. One example is duplicate names in external classes or libraries. This issue crops up when a programmer is using several external files within a single project. None of the external variables and functions can have the same name as other external variables or functions. To avoid this type of problem, third-party vendors frequently add prefixes or suffixes to variable and function names in order to reduce the likeliness of some other vendor's using the same name.

Obviously, the C++ gurus have come up with a solution to such scope-resolution problems. Otherwise, we wouldn't be having this discussion! The solution is user-defined namespaces, about which you'll study in this section.

Defining a Namespace

To accommodate user-defined namespaces, the keyword namespace was added to the C++ language. In its simplest form, a namespace is not unlike a structure or a class. You start the namespace definition with the namespace keyword, followed by the namespace's name and the declaration of the identifiers that'll be valid within the scope of that namespace.

Listing 30.17 shows a namespace definition. The namespace is called A and includes two identifiers, i, and j, and a function, Func(). Notice that the Func() function is completely defined within the namespace definition. You can also choose to define the function outside of the namespace definition, but in that case, you must prefix the function definition's name with the namespace's name, much as you would prefix a class's member-function definition with the class's name. Listing 30.18 shows this form of namespace function definition.


Listing 30.17  LST30_17.TXT-Defining a Namespace

namespace A

{

    int i;

    int j;



    int Func()

    {

        return 1;

    }

}



Listing 30.18  LST30_18.TXT-Defining a Function Outside of the Namespace Definition

namespace A

{

    int i;

    int j;



    int Func();

}



int A::Func()

{

    return 1;

}


NOTE
Namespaces must be defined at the file level of scope or within another namespace definition. They cannot be defined, for example, inside of a function.

Namespace Scope Resolution

Namespaces add a new layer of scope to your programs, but this means that you need some way of identifying that scope. The identification is, of course, the namespace's name, which you must use in your programs to resolve references to identifiers. For example, to refer to the variable i in namespace A, you'd write something like this:


A::i = 0;

You can, if you like, nest one namespace definition within another, as shown in Listing 30.19. In the case shown in the listing, however, you have to use more complicated scope resolutions in order to differentiate between the i variable declared in A and B, like this:


A::i = 0;

A::B::i = 0;


Listing 30.19  LST30_19.TXT-Nesting Namespace Definitions

namespace A

{

    int i;

    int j;



    int Func()

    {

        return 1;

    }



    namespace B

    {

        int i;

    }

}


If you're going to frequently reference variables and functions within namespace A, you can avoid using the A:: resolution by preceding the program statements with a using line, as shown in Listing 30.20.


Listing 30.20  LDT30_20.TXT-Resolving Scope with the using Keyword

using namespace A;

i = 0;

j = 0;

int num1 = Func();


Unnamed Namespaces

Just to be sure that you're thoroughly confused, Visual C++ allows you to have unnamed namespaces. You define an unnamed namespace exactly as you would any other namespace, except you leave off the name. Listing 30.21 shows the definition of an unnamed namespace.


Listing 30.21  LST30_21.TXT-Defining an Unnamed Namespace

namespace

{

    int i;

    int j;



    int Func()

    {

        return 1;

    }

}


You refer to the identifiers in the unnamed namespace without any sort of extra scope resolution, like this:


i = 0;

j = 0;

int num1 = Func();

Namespace Aliases

There may be times when you run into namespaces that have long names. In these cases, having to use that long name over and over in your program in order to access the identifiers defined in the namespace can be a major chore. To solve this problem, Visual C++ enables you to create namespace aliases, which are just replacement names for a namespace. You create an alias like this:


namespace A = LongName;

In the previous line of code, LongName is the original name of the namespace, and A is the alias. After the preceding line executes, you can access the LongName namespace using either A or LongName. You can think of an alias as a nickname. If your name is Robert, you probably respond to Bob, too, right? Listing 30.22 is a short program that demonstrates namespace aliases.


Listing 30.22  NAMESPACE.CPP-Using a Namespace Alias

namespace ThisIsANamespaceName

{

    int i;

    int j;



    int Func()

    {

        return 2;

    }

}



int main()

{

    namespace ns = ThisIsANamespaceName;



    ns::i = 0;

    ns::j = 0;

    int num1 = ns::Func();



    return 0;

}


From Here…

You've covered a lot of ground in this chapter, all of it dealing with advanced programming techniques. If you feel a little disoriented, read a comic book, and then come back for another pass at this chapter. The techniques covered here, although fairly new to C++ programming, are rapidly becoming must-know techniques for all C++ programmers. Exceptions and templates, especially, are things that you'll run into often when you examine other programmers' code-or, more importantly, when you apply for that programmer's job.

For more information on related topics, please refer to the following chapters: