Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Professional C++ [eng].pdf
Скачиваний:
284
Добавлен:
16.08.2013
Размер:
11.09 Mб
Скачать

Writing Generic Code with Templates

Inheritance versus Specialization

Some programmers find the distinction between template inheritance and template specialization confusing. The following table summarizes the differences.

 

Inheritance

Specialization

Reuses code?

Yes: subclasses contain all

No: you must rewrite all code in

 

superclass members and

the specialization.

 

methods.

 

Reuses name?

No: the subclass name

 

must be different from the

 

superclass name.

Yes: the specialization must have the same name as the original.

Supports polymorphism?

Yes: objects of the subclass

 

can stand in for objects of

 

the superclass.

No: each instantiation of a template for a type is a different type.

Use inheritance for extending implementations and for polymorphism. Use specialization for customizing implementations for particular types.

Function Templates

You can also write templates for stand-alone functions. For example, you could write a generic function to find a value in an array and return its index:

template <typename T>

int Find(T& value, T* arr, int size)

{

for (int i = 0; i < size; i++) { if (arr[i] == value) {

// Found it; return the index

return (i);

}

}

// Failed to find it; return -1 return (-1);

}

The Find() function template can work on arrays of any type. For example, you could use it to find the index of an int in an array of ints or a SpreadsheetCell in an array of SpreadsheetCells.

295

Chapter 11

You can call the function in two ways: explicitly specifying the type with angle brackets or omitting the type and letting the compiler deduce it from the arguments. Here are some examples:

int x = 3, intArr[4] = {1, 2, 3, 4};

double d1 = 5.6, dArr[4] = {1.2, 3.4, 5.7, 7.5};

int res;

res = Find(x, intArr, 4); // Calls Find<int> by deduction res = Find<int>(x, intArr, 4); // call Find<int> explicitly.

res = Find(d1, dArr, 4); // Call Find<double> by deduction.

res = Find<double>(d1, dArr, 4); // Calls Find<double> explicitly.

res = Find(x, dArr, 4); // DOES NOT COMPILE! Arguments are different types.

SpreadsheetCell c1(10), c2[2] = {SpreadsheetCell(4), SpreadsheetCell(10)};

res = Find(c1, c2, 2); // calls Find<SpreadsheetCell> by deduction res = Find<SpreadsheetCell>(c1, c2, 2); // Calls Find<SpreadsheetCell>

// explicitly.

Like class templates, function templates can take nontype parameters. For brevity, we only show an example of a type parameter for function templates.

The C++ standard library provides a templatized find() function that is much more powerful than the one above. See Chapter 22 for details.

Function Template Specialization

Just as you can specialize class templates, you can specialize function templates. For example, you might want to write a Find() function for char* C-style strings that compares them with strcmp() instead of operator==. Here is a specialization of the Find() function to do this:

template<>

int Find<char*>(char*& value, char** arr, int size)

{

for (int i = 0; i < size; i++) {

if (strcmp(arr[i], value) == 0) { // Found it; return the index

return (i);

}

}

// Failed to find it; return –1 return (-1);

}

You can omit the <char*> in the function name when the parameter type can be deduced from the arguments, making your prototype look like this:

template<>

int Find(char*& value, char** arr, int size)

296

Writing Generic Code with Templates

However, the deduction rules are tricky when you involve overloading as well (see next section), so, in order to avoid mistakes, it’s better to note the type explicitly.

Although the specialized find function could take just char* instead of char*& as its first parameter, it’s best to keep the arguments parallel to the nonspecialized version of the function for the deduction rules to function properly.

You can use the specialization like this:

char* word = “two”;

char* arr[4] = {“one”, “two”, “three”, “four”}; int res;

res = Find<char*>(word, arr, 4); // Calls the char* specialization res = Find(word, arr, 4); // Calls the char* specialization

Function Template Overloading

You can also overload template functions with nontemplate functions. For example, instead of writing a Find() template specialization for char*, you could write a nontemplate Find() function that works on char*s:

int Find(char*& value, char** arr, int size)

{

for (int i = 0; i < size; i++) {

if (strcmp(arr[i], value) == 0) { // Found it; return the index return (i);

}

}

// Failed to find it; return -1 return (-1);

}

This function is identical in behavior to the specialized version in the previous section. However, the rules for when it is called are different:

char* word = “two”;

char* arr[4] = {“one”, “two”, “three”, “four”}; int res;

res = Find<char*>(word, arr, 4); // Calls the Find template with T=char* res = Find(word, arr, 4); // Calls the Find nontemplate function!

Thus, if you want your function to work both when char* is explicitly specified and via deduction when it is not, you should write a specialized template version instead of a nontemplate, overloaded version.

Like template class method definitions, function template definitions (not just the prototypes) must be available to all source files that use them. Thus, you should put the definitions in header files if more than one source file uses them.

297

Chapter 11

Function Template Overloading and Specialization Together

It’s possible to write both a specialized Find() template for char*s and a stand-alone Find() function for char*s. The compiler always prefers the nontemplate function to a templatized version. However, if you specify the template instantiation explicitly, the compiler will be forced to use the template version:

char* word = “two”;

char* arr[4] = {“one”, “two”, “three”, “four”}; int res;

res = Find<char *>(word, arr, 4); // Calls the char* specialization of the // template

res = Find(word, arr, 4); // Calls the Find nontemplate function.

Friend Function Templates of Class Templates

Function templates are useful when you want to overload operators in a class template. For example, you might want to overload the insertion operator for the Grid class template to stream a grid.

If you are unfamiliar with the mechanics for overloading operator<<, consult Chapter 16 for details.

As discussed in Chapter 16, you can’t make operator<< a member of the Grid class: it must be a standalone function template. The definition, which should go directly in Grid.h, looks like this:

template <typename T>

ostream& operator<<(ostream& ostr, const Grid<T>& grid)

{

for (int i = 0; i < grid.mHeight; i++) { for (int j = 0; j < grid.mWidth; j++) {

// Add a tab between each element of a row.

ostr << grid.mCells[j][i] << “\t”;

}

ostr << std::endl; // Add a newline between each row.

}

return (ostr);

}

This function template will work on any Grid, as long as there is an insertion operator for the elements of the grid. The only problem is that operator<< accesses protected members of the Grid class. Therefore, it must be a friend of the Grid class. However, both the Grid class and the operator<< are templates. What you really want is for each instantiation of operator<< for a particular type T to be a friend of the Grid template instantiation for that type. The syntax looks like this:

//Grid.h

#include <iostream> using std::ostream;

//Forward declare Grid template. template <typename T> class Grid;

//Prototype for templatized operator<<. template<typename T>

ostream& operator<<(ostream& ostr, const Grid<T>& grid);

298