• No results found

about arrays already. However, if you don’t know about them, you may want to skip ahead and read the first part of Chapter 3 and then come back here.

1: int SumIntegers( int* p_array, int p_count ) 2: {

3: int index; 4: int sum = 0;

5: for( index = 0; index < p_count; index++ ) 6: sum += p_array[index];

7: return sum; 8: }

Line 3 defines the index variable, which will be used to access each item in p_array. On line 4, I define the sum variable, which is initially empty, and on lines 5 and 6, we loop through the array, adding each index to the sum. Lastly, on line 7, the sum is returned.

A little further down the line, you might want to do the same thing, but with floats. Without templates, you would probably just copy the code and replace the ints with floats, like this:

1: float SumFloats( float* p_array, int p_count ) 2: {

3: int index; 4: float sum = 0;

5: for( index = 0; index < p_count; index++ ) 6: sum += p_array[index];

7: return sum; 8: }

This is not too difficult, right? So what’s the problem? What happens if you need to change the way the function sums the numbers? Although this situation is not very likely with the given example, it happens all the time in real code. You’d have to go back and change every copy of the code that you’ve made. What a pain in the butt!

17

Template Functions

Doing It with Templates

C++ comes to the rescue by allowing us to create template functions, which use the same algorithm but operate on different datatypes. The syntax for a template func- tion is such:

template< class T >

returntype functionname( parameter list )

You first declare that you are creating a template by putting in the template key- word. You then put the class keyword and the name of the generic datatype after that, contained within the <> brackets. In the preceding example, T (which stands for “Template”) is the name of the generic datatype, and whenever I want to use the class in the function, I refer to it as T. After that, you write the function declara- tion the same way you normally would. In my examples, I separate the template declaration and the function declara-

tion into two lines, but you aren’t required to do that. Technically, they can be on the same line, but I prefer separating them because it makes the code more readable.

Let’s look at an example of a template

function by condensing the two sum functions into one template function called

sum:

T is called a in the

NOTE

parameterized type

world of software engineering.

1: template< class T >

2: T Sum( T* p_array, int p_count ) 3: {

4: int index; 5: T sum = 0;

6: for( index = 0; index < p_count; index++ ) 7: sum += p_array[index];

8: return sum; 9: }

On line 1, I use the template keyword to tell the compiler that I am creating a tem- plate function that will have one generic datatype as a parameter, henceforth

referred to as T. You can replace T with whatever name you want as long as it does not conflict with an existing class or type name. Some people would prefer to use more descriptive type names, such as DataType or SumType. Whatever name you choose should make sense and describe the usage of the datatype within the function.

a template function that calls its

foo foo

CAUTION

It is essential, upon choosing a name for your generic datatype within the template, that you choose one that does not conflict with an existing class name. For example, if you have generic class , but you also have a regular class named , the compil- er won’t like this and will barf error messages all over you.

On line 2, I declare the function signa- ture. It will return an instance of type T, and it takes a pointer of type T as a para- meter, which will be the array. Note how the count variable is an integer; there is no need to use a generic counting type because arrays are always indexed on discrete integer boundaries.

On line 4, I declare an integer index

variable, which will be used to access the appropriate items in the array. On line 5, I declare the sum variable to be of type T, meaning that the sum will be the same datatype as the items in the array. I also initialize it to the value ‘0’, which is important because the datatype T must have an overloaded assignment operator that takes a parameter of type int

(because the compiler treats the constant ‘0’ as an integer). If you are unfamiliar with operator overloads, please read about them in Appendix A, “A C++ Primer.” On line 6 and 7, I loop through the array and add every item in the array to the sum variable. Please note, however, that in order for line 7 to operate correctly, type T must have a working += operator. I go over the limitations of parameterized types in more detail in a later section.

On line 8, I simply return the sum variable.

Let’s see this new function in action! Let’s test it out on two different types of arrays! 1: void main() 2: { 3: int intarray[10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }; 4: float floatarray[9] = { 1.1f, 2.2f, 3.3f, 4.4f, 5.5f, 5: 6.6f, 7.7f, 8.8f, 9.9f }; 6:

7: // first sum the two arrays using the non-templated functions. 8: cout << “Using SumIntegers, the sum of intarray is: “;

9: cout << SumIntegers( intarray, 10 ) << endl;

10: cout << “Using SumFloats, the sum of floatarray is: “; 11: cout << SumFloats( floatarray, 9 ) << endl;

19

Template Classes

13: // now sum the two arrays using the templated function. 14: cout << “Using Sum, the sum of intarray is: “;

15: cout << Sum( intarray, 10 ) << endl;

16: cout << “Using Sum, the sum of floatarray is: “; 17: cout << Sum( floatarray, 9 ) << endl;

18: }

On lines 3 and 4, I declare the two arrays, one of type int and one of type float. On lines 8 through 11, I call the two non-templated sum functions SumIntegers and

SumFloats and output the results to the console.

Lastly, on lines 13 through 17, instead of using the two separate sum functions, I use the templated Sum function on each array, even though they are of two totally differ- ent datatypes! Magic? Nope, it’s one of C++’s niftier features.

Figure 2.2 shows Example 2-1 in action.

Figure 2.2

Screenshot for Example 2-1.The

Sum function was used on two different arrays with no problems.

Template Classes

A template class is similar to a template function, except that a template class is an entire class that operates on a generic datatype. I base most of my data structures on templates within this book, so you need to understand what a template class is. For example, say I want to create a class that is meant to retain a sum and have numerous types of data added to it. This is similar to the sum function I created in

the previous section; however, it is based on a class instead. The following classes can be found on the CD in the directory \examples\ch02\02 - Template Classes\)

1: class IntAdder 2: { 3: public: 4: // constructor 5: IntAdder() 6: { 7: m_sum = 0; 8: } 9: // add function

10: void Add( int p_number ) 11: {

12: m_sum += p_number; 13: }

14: // get sum function. 15: int Sum() 16: { 17: return m_sum; 18: } 19: private: 20: // sum variable. 21: int m_sum; 22: };

In the previous section, I declared a local variable sum to maintain the sum of the numbers as the function looped through the array. This time, I let the class main- tain a variable called m_sum (line 21) and keep track of it. On lines 5–8, I declare a constructor that initializes the m_sum variable to 0.

On lines 10–13 is the Add function, which takes an integer as a parameter and adds it to the m_sum variable.

The function Sum on lines 15–18 returns the current sum.

Say you now need the same functionality, but you need it to add floats instead of integers. You could copy and paste the entire class and create something that looks like this:

1: class FloatAdder 2: {

3: public:

21

Template Classes

5: FloatAdder() 6: { 7: m_sum = 0.0f; 8: } 9: // add function

10: void Add( float p_number ) 11: {

12: m_sum += p_number; 13: }

14: // get sum function. 15: float Sum() 16: { 17: return m_sum; 18: } 19: private: 20: // sum variable. 21: float m_sum; 22: };

In this class, there are three functions. The constructor clears the m_sum variable, Add

adds a number to the current sum, and Sum returns the value of the current sum. Look at how long the function is this time. It’s no longer a simple 8-line function, but an entire 22-line class, almost three times as large! What happens if the class is responsible for doing even more things (like computing an average as well)? What happens when the class is changed after you have already copied it and modified it to work with floats? Now that you see the problem, you’ll have to track down every single copy of the class that you’ve made and change each one! What a mess! Chances are likely that you won’t have every function in an organized manner, and each one will probably be placed somewhere that seemed appropriate at the time you coded it. However, you have no reliable way of tracking each and every copy of the code, so the code will be thrown around and separated by chaos, as in Figure 2.3!

Figure 2.3

The organization of a non- templated class tends to be chaotic because you almost never have all of the classes in the same file.

So instead of copying the entire class every time you want it to operate on a differ- ent datatype, you can create a templated class that operates on a single generic type, like this:

1: template< class T > 2: class Adder 3: { 4: public: 5: // constructor 6: Adder() 7: { 8: m_sum = 0; 9: } 10: // add function

11: void Add( T p_number ) 12: {

13: m_sum += p_number; 14: }

15: // get sum function. 16: T Sum() 17: { 18: return m_sum; 19: } 20: private: 21: // sum variable. 22: T m_sum; 23: };

On line 1, I declare that I am creating a template that will operate on one generic datatype, named T. Starting at line 2, I declare the class just as I usually would, except that it operates on type T instead of a specific datatype.

On line 8, I set the initial value of m_sum to 0, which, as before, requires that the datatype have an assignment operator capable of accepting an integer parameter. On line 13, I increment the m_sum variable, which requires that datatype T have a +=

operator.

This is the syntax required to declare an instance of the adder class that operates on integers:

23

Template Classes

The declaration of a template class instance is almost the same as declaring an instance of a normal class or datatype, except that a template class must have its parameterized types explicitly declared, within the arrow brackets, after the class name. In this example, I created an adder of type int, called intAdder. Here’s how to use the adder class:

1: void main() 2: { 3: IntAdder iadder1; 4: Adder<int> iadder2; 5: FloatAdder fadder1; 6: Adder<float> fadder2; 7: int i; 8: float f; 9: for( i = 0, f = 0.0f; i < 10; i++, f += 1.1f ) 10: { 11: iadder1.Add( i ); 12: iadder2.Add( i ); 13: fadder1.Add( f ); 14: fadder2.Add( f ); 15: }

16: cout << “The integer sum using an IntAdder: “ << iadder1.Sum() << endl; 17: cout << “The integer sum using an Adder: “ << iadder2.Sum() << endl; 18: cout << “The float sum using a FloatAdder: “ << fadder1.Sum() << endl; 19: cout << “The float sum using an Adder: “ << fadder2.Sum() << endl; 20: }

On lines 3–6, I create four adders, which I’ll use to keep track of sums.

On lines 7–11, I loop 10 times, telling the adders to add 10 different values, and then retrieve the final sums on lines 16–19. Neat, huh?

Figure 2.4 shows Example 2-2 in action. This is a Example 2-2. Figure 2.4 screenshot for

Multiple Parameterized

Types

Templates do not have to be based on a single generic datatype. A template class or function can have any number of parameterized types! You declare each type within the arrow brackets as such:

template< class one, class two, class three >

You must separate each datatype name by a comma within the brackets. This scenario is an example of a time when naming your generic datatypes with descrip- tive names becomes important because each generic type usually has a different purpose.

Functions and classes that have multiple template parameters are usually chunks of code in which you want to modify more than one datatype to suit different pur- poses. Without templates, it is even easier for your code to degenerate into pure chaos, as shown in Figure 2.5.

25

Multiple Parameterized Types

Figure 2.5

This demonstrates the chaos separating all the different kinds of classes without using templates.

Next, I’ll create a template function that determines the average of an array of arbitrary datatypes. (This class can be found on the CD in the directory \examples\ch02\03 - Multiple Parameters\ .)

1: template< class Sumtype, class Averagetype >

2: Averagetype Average( Sumtype* p_array, Averagetype p_count ) 3: {

4: int index; 5: Sumtype sum = 0;

6: for( index = 0; p_count > index; index++ ) 7: sum += p_array[index];

8: return (Averagetype)sum / p_count; 9: }

On line 1, I declare that I will be making a template that has two generic datatypes: a Sumtype and an Averagetype. The Sumtype is the datatype I will be summing, and the Averagetype is the datatype that I will be returning from the function.

On line 2, I declare that I am returning a value of type Averagetype and receiving an array of Sumtypes. Note also that the count is of type Averagetype because the average of a list is defined as the sum over the count.

On lines 6 and 7, I loop through the list, just like the Sum template function I defined earlier, except for one small difference. Because the p_count variable is no longer of definite type, it must support a > (greater-than) operator that compares itself to an int.

The rest of this function is the same as the Sum template function I created earlier, with one exception: On line 8, I convert the local variable sum into an Averagetype, divide the sum by the count, and return the result. This line assumes that it is possi- ble to convert an instance of Sumtype into an Averagetype.

Here, you can see this function in action:

1: void main() 2: {

3: int array[10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };

4: cout << “Average( array, 10 ) = “ << Average( array, 10 ) << endl; 5: cout << “Average( array, 10.0f ) = “ << Average( array, 10.0f ) << endl; 6: }

An array of integers is defined on line 3. On lines 4 and 5, the Average function is called with two different sets of parameters. The first one is called with 10 as the sec- ond parameter, and the second one is

called with 10.0f as the second parame- ter. Because C++ treats 10 as an int and

10.0f as a float, the two functions are called with two different sets of template parameters: <int, int> and <int, float>. The compiler determines which

datatypes to use at compile time by ana- lyzing the parameters of the function. Because 10.0f is passed in on line 5, the compiler treats that as a float, calls the

<int, float> version, and returns the average as a float.

The results of the example are shown in Figure 2.6.

The compiler determines the types

of a template function implicitly

meters.

NOTE

. In plain English, the compiler analyzes the datatypes that are passed into the function and creates the appro- priate template.The type of a tem- plate function is never determined by its return value, only by the para-

Example 2-3.

Figure 2.6

27

Using Values as Template Parameters

Using Values as Template

Parameters

Until now, you’ve only seen me using datatypes as parameters for a template func- tion or class. However, you don’t necessarily need to use datatypes as parameters; C++ allows you to use values of a particular datatype. C++ is so flexible that it even allows you to use a value of a generic datatype as a template parameter.

Using Values of a Specific

Datatype

First, let me show you how to declare a template parameter with a value of a spe- cific datatype. Templates of this type are declared as such:

template< datatype value >

where datatype is a datatype and value is a specific value of that type. Note that because templates are a compile-time feature, the value in a template parameter must be resolved at compile time; that is, you cannot create a template based on a variable.

Try using this feature by creating a simple fixed-length array class. (You can find this class in the directory \examples\ch02\04 - Values as Parameters\ on the CD; don’t confuse it with the Array class of the same name found in the \structures\

directory. Arrays will be discussed in far more detail in Chapter 3.)

1: template< class Datatype, int size > 2: class Array

3: { 4: public:

5: // set function, sets an index

6: void Set( Datatype p_item, int p_index ) 7: {

8: m_array[p_index] = p_item; 9: }

10: // get function, gets an index 11: Datatype Get( int p_index ) 12: {

13: return m_array[p_index]; 14: }

16: // the array.

17: Datatype m_array[size]; 18: };

On line 1, I declare that I am creating a template that will have one generic datatype, Datatype, and one integer value, size. On lines 6–14, define the

Set and Get functions, which set an item in the array or get an item in the array. The most important part of this class declaration is on line 17: I declare an array of Datatype with a size of size, which will never change.

Figure 2.7 shows how three different

Array classes are created, using different parameters.

Note that template classes with dif-

Array<int,5> as a para-

Array<int,4>

NOTE

ferent value parameters are consid- ered totally different types. For example, if you create a function that takes an

meter and you try passing an into it, the compiler will give you an error.

Figure 2.7

The Array class with three different parameter configurations. Remember that doubles are twice as large as ints. Here it is in action: 1: void main() 2: { 3: Array<int, 5> iarray5; 4: Array<int, 10> iarray10; 5: Array<float, 15> farray15; 6: iarray5.Set( 10, 0 ); 7: iarray5.Set( 3, 1 ); 8: iarray10.Set( 11, 9 ); 9: iarray10.Set( 2, 4 ); 10: farray15.Set( 10.1f, 3 );

29

Using Values as Template Parameters

11: farray15.Set( 3.1415f, 14 );

12: cout << “iarray5.Get( 0 ) = “ << iarray5.Get( 0 ) << endl; 13: cout << “iarray5.Get( 1 ) = “ << iarray5.Get( 1 ) << endl; 14: cout << “iarray10.Get( 9 ) = “ << iarray10.Get( 9 ) << endl; 15: cout << “iarray10.Get( 4 ) = “ << iarray10.Get( 4 ) << endl; 16: cout << “farray15.Get( 3 ) = “ << farray15.Get( 3 ) << endl;

Related documents