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

Writing Generic Code with Templates

You do not need to specify the default values for WIDTH and HEIGHT in the template specification for the method definitions. For example, here is the implementation of setElementAt():

template <typename T, int WIDTH, int HEIGHT>

void Grid<T, WIDTH, HEIGHT>::setElementAt(int x, int y, const T& inElem)

{

mCells[x][y] = inElem;

}

Now, you can instantiate a Grid with only the element type, the element type and the width, or the element type, width, and height:

Grid<int> myGrid;

Grid<int, 10> anotherGrid;

Grid<int, 10, 10> aThirdGrid;

The rules for default parameters in template parameter lists are the same as for functions or methods: you can provide defaults for parameters in order starting from the right.

Method Templates

C++ allows you to templatize individual methods of a class. These methods can be inside a class template or in a nontemplatized class. When you write a templatized class method, you are actually writing many different versions of that method for many different types. Method templates come in useful for assignment operators and copy constructors in class templates.

Virtual methods and destructors cannot be templatized.

Consider the original Grid template with only one parameter: the element type. You can instantiate grids of many different types, such as ints and doubles:

Grid<int> myIntGrid;

Grid<double> myDoubleGrid;

However, Grid<int> and Grid<double> are two different types. If you write a function that takes an object of type Grid<double>, you cannot pass a Grid<int>. Even though you know that an int grid could be copied to a double grid, because the ints could be coerced into doubles, you cannot assign an object of type Grid<int> to one of type Grid<double> or construct a Grid<double> from a Grid<int>. Neither of the following two lines compiles:

myDoubleGrid = myIntGrid; // DOES NOT COMPILE

Grid<double> newDoubleGrid(myIntGrid); // DOES NOT COMPILE

The problem is that the Grid template copy constructor and operator= signatures look like this:

Grid(const Grid<T>& src);

Grid<T>& operator=(const Grid<T>& rhs);

285

Chapter 11

The Grid copy constructor and operator= both take a reference to a const Grid<T>. When you instantiate a Grid<double> and try to call the copy constructor and operator=, the compiler generates methods with these signatures:

Grid(const Grid<double>& src);

Grid<double>& operator=(const Grid<double>& rhs);

Note that there are no constructors or operator= that take a Grid<int> within the generated Grid<double> class. However, you can rectify this oversight by adding templatized versions of the copy constructor and operator= to the Grid class to generate routines that will convert from one grid type to another. Here is the new Grid class definition:

template <typename T> class Grid

{

public:

Grid(int inWidth = kDefaultWidth, int inHeight = kDefaultHeight); Grid(const Grid<T>& src);

template <typename E>

Grid(const Grid<E>& src); ~Grid();

Grid<T>& operator=(const Grid<T>& rhs); template <typename E>

Grid<T>& operator=(const Grid<E>& rhs);

void setElementAt(int x, int y, const T& inElem); T& getElementAt(int x, int y);

const T& getElementAt(int x, int y) const;

int getHeight() const { return mHeight; } int getWidth() const { return mWidth; }

static const int kDefaultWidth = 10; static const int kDefaultHeight = 10;

protected:

void copyFrom(const Grid<T>& src); template <typename E>

void copyFrom(const Grid<E>& src);

T** mCells;

int mWidth, mHeight;

};

Member templates do not replace nontemplate members with the same name. This rule leads to problems with the copy constructor and operator= because of the compiler-generated versions. If you write templatized versions of the copy constructor and operator= and omit nontemplatized versions, the compiler will not call these new templatized versions for assignments of grids with the same type. Instead, it will generate a copy constructor and operator= for creating and assigning two grids of the same type, which will not do what you want! Thus, you must keep the old nontemplatized copy constructor and operator= as well.

286

Writing Generic Code with Templates

Examine the new templatized copy constructor signature first:

template <typename E> Grid(const Grid<E>& src);

You can see that there is another template declaration with a different typename, E (short for “element”). The class is templatized on one type, T, and the new copy constructor is also templatized on a different type, E. This twofold templatization allows you to copy grids of one type to another.

Here is the definition of the new copy constructor:

template <typename T> template <typename E>

Grid<T>::Grid(const Grid<E>& src)

{

copyFrom(src);

}

As you can see, you must declare the class template line (with the T parameter) before the member template line (with the E parameter). You can’t combine them like this:

template <typename T, typename E> // INCORRECT TEMPLATE PARAMETER LIST!

Grid<T>::Grid(const Grid<E>& src)

Some compilers require that you provide method template definitions inline in the class definition, but the C++ standard permits method template definitions outside the class definition.

The copy constructor uses the protected copyFrom() method, so the class needs a templatized version of copyFrom() as well:

template <typename T> template <typename E>

void Grid<T>::copyFrom(const Grid<E>& src)

{

int i, j;

mWidth = src.getWidth();

mHeight = src.getHeight();

mCells = new T* [mWidth];

for (i = 0; i < mWidth; i++) { mCells[i] = new T[mHeight];

}

for (i = 0; i < mWidth; i++) {

for (j = 0; j < mHeight; j++) { mCells[i][j] = src.getElementAt(i, j);

}

}

}

In addition to the extra template parameter line before the copyFrom() method definition, note that you must use public accessor methods getWidth(), getHeight(), and getElementAt() to access the

287

Chapter 11

elements of src. That’s because the object you’re copying to is of type Grid<T>, and the object you’re copying from is of type Grid<E>. They will not be the same type, so you must resort to public methods.

The final templatized method is the assignment operator. Note that it takes a const Grid<E>& but returns a Grid<T>&.

template <typename T> template <typename E>

Grid<T>& Grid<T>::operator=(const Grid<E>& rhs)

{

// Free the old memory.

for (int i = 0; i < mWidth; i++) { delete [] mCells[i];

}

delete [] mCells;

// Copy the new memory. copyFrom(rhs);

return (*this);

}

You do not need to check for self-assignment in the templatized assignment operator, because assignment of the same types still happens in the old, nontemplatized, version of operator=, so there’s no way you can get self-assignment here.

In addition to the confusing syntax for method templates, there is another problem: some compilers don’t implement full (or any) support for them. Try this example out on your compiler of choice to see if you can use these features.

Method Templates with Nontype Parameters

In the earlier example with integer template parameters for HEIGHT and WIDTH, we noted that a major problem is that the height and width become part of the types. This restriction prevents you from assigning a grid with one height and width to a grid with a different height and width. In some cases, however, it’s desirable to assign or copy a grid of one size to a grid of a different size. Instead of making the destination object a perfect clone of the source object, you would copy only those elements from the source array that fit in the destination array, padding the destination array with default values if the source array is smaller in either dimention. With method templates for the assignment operator and copy constructor, you can do exactly that, thus allow assignment and copying of different sized grids. Here is the class definition:

template <typename T, int WIDTH = 10, int HEIGHT = 10> class Grid

{

public: Grid() {}

template <typename E, int WIDTH2, int HEIGHT2> Grid(const Grid<E, WIDTH2, HEIGHT2>& src);

template <typename E, int WIDTH2, int HEIGHT2>

Grid<T, WIDTH, HEIGHT>& operator=(const Grid<E, WIDTH2, HEIGHT2>& rhs);

288

Writing Generic Code with Templates

void setElementAt(int x, int y, const T& inElem); T& getElementAt(int x, int y);

const T& getElementAt(int x, int y) const; int getHeight() const { return HEIGHT; } int getWidth() const { return WIDTH; }

protected:

template <typename E, int WIDTH2, int HEIGHT2>

void copyFrom(const Grid<E, WIDTH2, HEIGHT2>& src);

T mCells[WIDTH][HEIGHT];

};

We have added method templates for the copy constructor and assignment operator, plus a helper method copyFrom(). Recall from Chapter 8 that when you write a copy constructor, the compiler stops generating a default constructor for you, so we had to add a default constructor as well. Note, however, that we do not need to write nontemplatized copy constructor and assignment operator methods because the compiler-generated ones continue to be generated. They simply copy or assign mCells from the source to the destination, which is exactly the semantics we want for two grids of the same size.

When you templatize the copy constructor, assignment operator, and copyFrom(), you must specify all three template parameters. Here is the templatized copy constructor:

template <typename T, int WIDTH, int HEIGHT> template <typename E, int WIDTH2, int HEIGHT2>

Grid<T, WIDTH, HEIGHT>::Grid(const Grid<E, WIDTH2, HEIGHT2>& src)

{

copyFrom(src);

}

Here are the implementations of copyFrom() and operator=. Note that copyFrom() copies only WIDTH and HEIGHT elements in the x and y dimensions, respectively, from src, even if src is bigger than that. If src is smaller in either dimension, copyFrom() pads the extra spots with zero-initialized values. T() calls the default constructor for the object if T is a class type, or generates 0 if T is a simple type. This syntax is called the zero-initialization syntax. It’s a good way to provide a reasonable default value for a variable whose type you don’t yet know.

template <typename T, int WIDTH, int HEIGHT> template <typename E, int WIDTH2, int HEIGHT2>

void Grid<T, WIDTH, HEIGHT>::copyFrom(const Grid<E, WIDTH2, HEIGHT2>& src)

{

int i, j;

for (i = 0; i < WIDTH; i++) {

for (j = 0; j < HEIGHT; j++) {

if (i < WIDTH2 && j < HEIGHT2) { mCells[i][j] = src.getElementAt(i, j);

} else {

mCells[i][j] = T();

}

}

}

}

289