When Inheritance Isn t Enough

[Previous] [Next]

The invention of the C++ class has been a great boon for developers. Using C++ and the notion of a class, we can model the world as classes; that is, we can factor functionality into layers and achieve a reasonable amount of code reuse. For example, some classes will have a lot of the same functionality. C++ lets you factor that functionality into some base classes.

The canonical example is to model certain kinds of animals using C++ classes, such as dog and cat classes. The seasoned C++ developer will naturally create a hierarchy of classes. In the classic animal examples, the most basic functionality might be implemented in a base class named CAnimal:

 class CAnimal { public:     CAnimal() {     }     ~CAnimal() {     }     void Eat();     void Sleep();     CAnimal* Procreate(); }; class CMammal : public CAnimal {     HAIRFOLLICLES m_hair[1000000]; public:     CMammal() {     }     ~CMammal() {     }     void ProduceMilk(); }; class CCat: public CMammal { public:     CCat() {     }     virtual ~CCat();     virtual void MakeNoise() {         // "Meow"     } }; class CDog : public CMammal { public:     CDog() {     }     virtual ~CDog();     virtual void MakeNoise() {         // "Woof"     } }; 

The power of C++ comes from the fact that you don't have to type the source code for common functions over and over again. Instead, you override the behavior of these functions only when necessary. Although the inheritance and encapsulation capability of C++ is very powerful, at times it isn't quite enough. In some situations, inheritance might even be inappropriate.

Inheritance implies an "is a" relationship between two classes. For example, deriving the CDog class from the CMammal class implies that a dog "is a" mammal. That approach works great for such broad generalizations. However, sometimes we'd like to generalize about a "has a" relationship between two classes. For example, a dog has certain body organs, such as a heart, lungs, a stomach, and so on. It wouldn't make sense to derive a CDog class from these parts. But it would make sense to say a dog is composed of these parts. Furthermore, a dog isn't composed of just any old body parts, but of dog body parts. Templates give you a way to generalize the "has a" relationships between your classes. When inheritance and encapsulation won't solve your problems for one reason or another, you can turn to C++ templates.

Like C++ inheritance, C++ templates represent a way to abstract and generalize C++ code. To illustrate the use of C++ templates, we'll look at an all-too-common example—what happens when you write a really great class and debug it thoroughly, only to find out that your pointy-haired boss says you need to change it so that it works on a new kind of data.

A Canonical Template Example

In writing this book, we discovered that federal law requires us to provide this example when talking about C++ templates. Maybe that claim is stretching the truth a bit, but the following example does express the letter and the spirit behind C++ templates.

Imagine that you are required to write some code that represents a self-managing array of integers. Let's say that the array is a C++ class with a data member representing the array of integers and member functions that let you add and remove elements. All the code for allocating, reallocating, and destroying the array is encapsulated inside the class. The class will look something like this:

 class DynIntArray { public:     DynIntArray();  // Allocates memory     ~DynIntArray(); // Cleans up and does memory management     int Add(int n); // Adds an element and does                     //  memory management     void Remove(int nIndex) // Removes an element and                              //  does memory management     int GetAt(nIndex) const;     int GetSize(); private:     int* m_pIntArray;     int m_nArraysize; }; 

If you've been programming for a while, you've undoubtedly written a class that resembles this one. Why should you write malloc and free statements all over the place to resize the array when you can easily package all that stuff into a C++ class? C++ lets you encapsulate these operations and hide them from the client.

So let's say you've spent a considerable amount of time debugging this class and perfecting it. Just as you're adding the finishing touches, your clueless boss comes in and says he needs you to write the class so that it represents an array of floating numbers. Should you be alarmed? Just what is the difference between the class you wrote to manage integers and the class you need to write to manage an array of floating point numbers? The answers to these questions are, respectively, "No" and "Very little." The only difference between the two classes is the type of data being managed.

Your first reaction to needing to retool the class might be simply to take the class you wrote for integers and use editor inheritance to implement a dynamic array of floating numbers (that is, use copy and paste). A better way to transition the class, however, is to use C++ templates. C++ templates provide a way to generalize your code to work with many data types. The following code shows how you might use C++ templates to generalize the dynamic array to work with multiple data types:

 template <typename T>  class DynArray { public:     DynArray();     ~DynArray(); // Cleans up and does memory management     int Add(T Element); // Adds an element and does                         //  memory management     void Remove(int nIndex) // Removes an element and                              //  does memory management     T GetAt(nIndex) const;     int GetSize(); private:     T* TArray;     int m_nArraysize; }; 

Templates look a bit weird at first glance. But you'll get used to them quickly once you understand the syntax. C++ template syntax involves using the keyword template before describing a data type. In the preceding example, notice that the keyword template appears before the DynArray class declaration. This keyword tells the compiler that whatever data type description follows uses the template. The second part of the template syntax uses angle braces, which indicate the template's parameters to the compiler.

The preceding example also illustrates generalizing the DynArray on the data type managed by the class. Notice that instead of hard-coding the int data type, the DynArray manages a data type represented by the capital letter T. (The template syntax doesn't require you to use the capital letter T—it's just tradition.) Here's how you would use the DynArray class:

 void UseDynArray() {      DynArray<int> intArray;     DynArray<float> floatArray;     intArray.Add(4);     floatArray.Add(5.0);     intArray.Remove(0);     floatArray.Remove(0);     int x = intArray.GetAt(0);     float f = floatArray.GetAt(0); } 

Notice how using the DynArray involves instantiating instances of the DynArray based on a type. The client code isn't limited to using floating point numbers or integers. It can specify any simple data type, structure, or class and the DynArray will work just fine. In many ways, using templates in this way is like using macros and is virtually the same as writing a version of the DynArray for each data type the client needs to manage. The critical difference is that templates are type-safe. The preprocessor does a straight substitution when it sees a macro. Templates are a bit different from macros, however, because they have to go through the compiler and are subject to all the rigorous type checking the compiler imposes.

C++ templates can be applied to solve many programming problems, and they turn out to be especially useful when cranking out COM code. As we just saw, templates are useful for writing one piece of source code that works with any type of data. They're also handy for writing smart pointers, creating parameterized algorithms, and mixing functionality into your class. Let's examine these various uses, starting with pointers.



Inside Atl
Inside ATL (Programming Languages/C)
ISBN: 1572318589
EAN: 2147483647
Year: 1998
Pages: 127

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net