Reviewing Template-Based Programming

 < Free Open Study > 



Inheritance is the classical way to reuse source code in OO-based languages. By creating base and derived class relationships, we are able to create hierarchies of classes. As we have seen, base classes tend to have a number of virtual and/or pure virtual functions that enforce a given behavior to each descendent. This is a good thing, of course, as the base class(es) can contain code which is easily reused (and possible reinterpreted) down the inheritance chain. However, there is another form of code reuse.

C++ templates allow you to create classes based off parameterized types. For example, imagine you were told to create a C++ class that can maintain a dynamic array of integers. You might begin by creating a C++ class that defines a private array of integers, which may be manipulated using a small set of public methods:

// The dynamic integer class. class CIntArray { public:      void Add(int newInt);      // Insert a new int into the array.      void Remove(int index);    // Remove int at position 'index'.      int Count();               // Get the current size of the array.      int Item(int index);       // Retrieve item 'index' from the array. private:      int* m_arrayOfInts;        // Array of integers.      int m_position;            // Current position in the array.      int m_size;                // Current size of the array. };

As you can see, this C++ class allows the developer to add, remove, and reference a specific index in the array, as well as obtain the current count. Life appears well.

Now assume you wish to create another dynamic array class which operates on an underlying array of floats rather than integers. One option you have is to create another class, CFloatArray, which has the same member functions as CIntArray but manipulates a suitable array of floats:

// The dynamic float class. class CFloatArray { public:      void Add(float newFloat);  // Insert a new float into the array.      void Remove(int index);    // Remove float at position 'index'.      int Count();               // Get the current size of the array.      float Item(int index);     // Grab out item 'index' from the array. private:      float* m_arrayOfFloats;    // Array of floats.      int m_position;            // Current position in the array.      int m_size;                // Current size of the array. };

This approach leaves you with two C++ classes that essentially do the same thing. The situation could grow worse if you decide to create unique classes to represent shorts, longs, strings (char*), and so forth. Template-based programming allows you to create a generic C++ shell, which fills in the specifics by using a placeholder for various parts of the actual class definition. In many respects, template programming has a similar feel to C macros (#define). However, unlike magic macros, templates are type safe. To create a C++ template that abstracts away the differences between floats, integers, __int64s, shorts, doubles, and longs (among others), you could create the following:

// A C++ template which operates on 'some type'. template <typename Type> class CArray { public:      void Add(Type newType);    // Insert a new Type into the array.      void Remove(int index);    // Remove Type at position 'index'.      int Count();               // Get the current size of the array.      Type Item(int index);      // Grab out item 'index' from array. private:      Type * m_arrayOfTypes;     // An array of some type.      int m_position;            // Position in the array.      int m_size;                // Current size of the array. };

Using a C++ template, we do not need to create a separate class to represent the minor code variations used to work with arrays of different types. The CArray class takes a single template parameter named Type, which is used as a placeholder throughout the class definition:

// Create some dynamic arrays using the CArray template. void main() {      // Make an array of integers.      CArray<int> intArray;      intArray.Add(90);      cout << "Int array has: " << intArray.Count() << "items inside." << endl;      // Make an array of floats.      CArray<float> floatArray;      floatArray.Add(30.8);           cout << "Float array has: " << floatArray.Count() << " items inside." << endl; }

The beauty of this approach is that we can delay the specification of the type of data operated on until building the array template. In this example, the Type placeholder will "expand" to an integer, float, or what have you, based on the needs of the client.

Templates as Containers

We may also specify a C++ class as a template parameter. This approach allows us to create a generic "collection" object. For example, assume we have a number of C++ classes each representing a geometric shape. CHexagon, CSquare, and CCircle each have a class definition that looks like the following:

// Each of our shape classes defines a Draw() method. class CHexagon { public:      void Draw();          // Draw this shape.      CHexagon();      virtual ~CHexagon(); };

 On the CD   The shapes template example is included on your CD-ROM under the Labs\Chapter 01\DrawTemplate subdirectory.

Now assume we wanted to create a template class which could take as a parameter any valid shape object, and invoke the correct Draw() method based on the placeholder class:

// CDrawTemplate renders any item defining a Draw() method. template< typename TShape > class CDrawTemplate { private:      TShape theShape; public:      CDrawTemplate (){}      virtual ~ CDrawTemplate (){}      // Draw whichever shape we have.      void DrawTheShape()      {           theShape.Draw();      } };

A C++ client could now send in any object containing a Draw() method as a template parameter to the CDrawTemplate class:

// Draw some objects with our C++ template. int main(int argc, char* argv[]) {      // Create a templatized hexagon.      CDrawTemplate<CHexagon> h;      h.DrawTheShape();      // Create a templatized square.      CDrawTemplate<CSquare> s;      s.DrawTheShape();      return 0; } 

If we assume each shape object implements Draw() using a simple cout statement, we could assume the output shown in Figure 1-22:

click to expand
Figure 1-22: Template output.

When we begin our examination of the Active Template Library we will see just how helpful templates can be to lighten the burden of creating boilerplate COM code. Until Chapter 6, just keep in mind that C++ templates can be used to create a generic class that is able to operate on "some type" specified by the template parameter set.

In these trivial examples, we examined templates specifying a single parameter. This is not a requirement of template-based programming. In fact, some ATL template classes take up to eight different parameters, each of which serves as a placeholder used throughout the class implementation.

That wraps up our rapid-fire C++ refresher. Chapter 2 will introduce you to another popular programming paradigm: interface-based development. The interface is an odd little creature living at the dead center of the COM universe, whose importance warrants a full discussion.



 < Free Open Study > 



Developer's Workshop to COM and ATL 3.0
Developers Workshop to COM and ATL 3.0
ISBN: 1556227043
EAN: 2147483647
Year: 2000
Pages: 171

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