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

Chapter 11

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

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

{

//No need to check for self-assignment because this version of

//assignment is never called when T and E are the same

//No need to free any memory first

//Copy the new memory.

copyFrom(rhs); return (*this);

}

Template Class Specialization

You can provide alternate implementations of class templates for specific types. For example, you might decide that the Grid behavior for char*s (C-style strings) doesn’t make sense. The grid currently stores shallow copies of pointer types. For char*’s, it might make sense to do a deep copy of the string.

Alternate implementations of templates are called template specializations. Again, the syntax is a little weird. When you write a template class specialization, you must specify that it’s a template, and that you are writing the version of the template for that particular type. Here is the syntax for specializing the original version of the Grid for char *s.

//#includes for working with the C-style strings. #include <cstdlib>

#include <cstring> using namespace std;

//When the template specialization is used, the original template must be visible

//too. #including it here ensures that it will always be visible when this

//specialization is visible.

#include “Grid.h”

template <> class Grid<char*>

{

public:

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

~Grid();

Grid<char*>& operator=(const Grid<char*>& rhs);

void setElementAt(int x, int y, const char* inElem); char* 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;

290

Writing Generic Code with Templates

protected:

void copyFrom(const Grid<char*>& src);

char*** mCells;

int mWidth, mHeight;

};

Note that you don’t refer to any type variable, such as T, in the specialization: you work directly with char*s. One obvious question at this point is why this class is still a template. That is, what good is this syntax?

template <>

class Grid<char *>

This syntax tells the compiler that this class is a char * specialization of the Grid class. Suppose that you didn’t use that syntax and just tried to write this:

class Grid

The compiler wouldn’t let you do that because there is already a class named Grid (the original template class). Only by specializing it can you reuse the name. The main benefit of specializations is that they can be invisible to the user. When a user creates a Grid of ints or SpreadsheetCells, the compiler generates code from the original Grid template. When the user creates a Grid of char*’s, the compiler uses the char* specialization. This can all be “behind the scenes.”

Grid<int> myIntGrid; // Uses original Grid template

Grid<char*> stringGrid1(2, 2); // Uses char* specialization

char* dummy = new char[10];

strcpy(dummy, “dummy”);

stringGrid1.setElementAt(0, 0, “hello”); stringGrid1.setElementAt(0, 1, dummy); stringGrid1.setElementAt(1, 0, dummy); stringGrid1.setElementAt(1, 1, “there”);

delete[] dummy;

Grid<char*> stringGrid2(stringGrid1);

When you specialize a template, you don’t “inherit” any code: specializations are not like subclasses. You must rewrite the entire implementation of the class. There is no requirement that you provide methods with the same names or behavior. In fact, you could write a completely different class with no relation to the original! Of course, that would abuse the template specialization ability, and you shouldn’t do it without good reason. Here are the implementations for the methods of the char* specialization. Unlike in the original template definitions, you do not repeat the template<> syntax before each method or static member definition!

const int Grid<char*>::kDefaultWidth; const int Grid<char*>::kDefaultHeight;

Grid<char*>::Grid(int inWidth, int inHeight) :

291

Chapter 11

mWidth(inWidth), mHeight(inHeight)

{

mCells = new char** [mWidth];

for (int i = 0; i < mWidth; i++) { mCells[i] = new char* [mHeight]; for (int j = 0; j < mHeight; j++) {

mCells[i][j] = NULL;

}

}

}

Grid<char*>::Grid(const Grid<char*>& src)

{

copyFrom(src);

}

Grid<char*>::~Grid()

{

// Free the old memory.

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

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

}

delete[] mCells[i];

}

delete[] mCells;

}

void Grid<char*>::copyFrom(const Grid<char*>& src)

{

int i, j;

mWidth = src.mWidth; mHeight = src.mHeight;

mCells = new char** [mWidth]; for (i = 0; i < mWidth; i++) {

mCells[i] = new char* [mHeight];

}

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

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

if (src.mCells[i][j] == NULL) { mCells[i][j] = NULL;

} else {

mCells[i][j] = new char[strlen(src.mCells[i][j]) + 1]; strcpy(mCells[i][j], src.mCells[i][j]);

}

}

}

}

Grid<char*>& Grid<char*>::operator=(const Grid<char*>& rhs)

{

int i, j;

292

Writing Generic Code with Templates

//Check for self-assignment. if (this == &rhs) {

return (*this);

}

//Free the old memory.

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

for (j = 0; j < mHeight; j++) { delete[] mCells[i][j];

}

delete[] mCells[i];

}

delete[] mCells;

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

return (*this);

}

void Grid<char*>::setElementAt(int x, int y, const char* inElem)

{

delete[] mCells[x][y]; if (inElem == NULL) {

mCells[x][y] = NULL;

} else {

mCells[x][y] = new char[strlen(inElem) + 1]; strcpy(mCells[x][y], inElem);

}

}

char* Grid<char*>::getElementAt(int x, int y) const

{

if (mCells[x][y] == NULL) { return (NULL);

}

char* ret = new char[strlen(mCells[x][y]) + 1]; strcpy(ret, mCells[x][y]);

return (ret);

}

getElementAt() returns a deep copy of the string, so you don’t need an overload that returns a const char*.

Subclassing Template Classes

You can write subclasses of template classes. If the subclass inherits from the template itself, it must be a template as well. Alternatively, you can write a subclass to inherit from a specific instantiation of the template class, in which case your subclass does not need to be a template. As an example of the former, suppose you decide that the generic Grid class doesn’t provide enough functionality to use as a game board. Specifically, you would like to add a move() method to the game board that moves a piece from one location on the board to another. Here is the class definition for the GameBoard template:

293

Chapter 11

#include “Grid.h”

template <typename T>

class GameBoard : public Grid<T>

{

public:

GameBoard(int inWidth = Grid<T>::kDefaultWidth, int inHeight = Grid<T>::kDefaultHeight);

void move(int xSrc, int ySrc, int xDest, int yDest);

};

This GameBoard template subclasses the Grid template, and thereby inherits all its functionality. You don’t need to rewrite setElementAt(), getElementAt(), or any of the other methods. You also don’t need to add a copy constructor, operator=, or destructor, because you don’t have any dynamically allocated memory in the GameBoard. The dynamically allocated memory in the Grid superclass will be taken care of by the Grid copy constructor, operator=, and destructor.

The inheritance syntax looks normal, except that the superclass is Grid<T>, not Grid. The reason for this syntax is that the GameBoard template doesn’t really subclass the generic Grid template. Rather, each instantiation of the GameBoard template for a specific type subclasses the Grid instantiation for that type. For example, if you instantiate a GameBoard with a ChessPiece type, then the compiler generates code for a Grid<ChessPiece> as well. The “: public Grid<T>” syntax says that this class subclasses from whatever Grid instantiation makes sense for the T type parameter. Note that the C++ name lookup rules for template inheritance require you to specify that kDefaultWidth and kDefaultHeight are declared in, and thus dependent on, the Grid<T> superclass.

Here are the implementations of the constructor and the move method. Again, note the use of Grid<T> in the call to the superclass constructor. Additionally, although many compilers don’t enforce it, the name lookup rules require you to use the this pointer to refer to data members and methods in the superclass.

template <typename T>

GameBoard<T>::GameBoard(int inWidth, int inHeight) : Grid<T>(inWidth, inHeight)

{

}

template <typename T>

void GameBoard<T>::move(int xSrc, int ySrc, int xDest, int yDest)

{

this->mCells[xDest][yDest] = this->mCells[xSrc][ySrc]; this->mCells[xSrc][ySrc] = T(); // zero-initialize the src cell

}

As you can see, move() uses the zero-initializtion syntax T() described in the section on “Method Templates with Nontype Parameters.”

You can use the GameBoard template like this:

GameBoard<ChessPiece> chessBoard;

ChessPiece pawn; chessBoard.setElementAt(0, 0, pawn); chessBoard.move(0, 0, 0, 1);

294