C++模板编程
807 浏览 6 years, 1 month
2.6 模板类特例化
版权声明: 转载请注明出处 http://www.codingsoho.com/对于特定的类型,可以给类模板提供不同的实现。例如,您可能判断出Grid的行为对char*
(C风格的字符串)来说没有意义。目前这个网格保存的是指针类型的浅复制。对于char*
来说,对字符串进行深复制才有意义。
模板的另一个实现称为模板特例化(template specialization)。模板特例化的语法也有一点奇怪。
编写一个模板类特例化的时候,必须指明这是一个模板,以及正在为那个特定的类型编写这个版本模板。下面是为char*
特例化原始版本的Grid的语法:
template <>
class Grid<char*>
{
public:
Grid(size_t inWidth = kDefaultWidth, size_t inHeight = kDefaultHeight);
Grid(const Grid<char*>& src);
virtual ~Grid();
Grid<char*>& operator=(const Grid<char*>& rhs);
//
void setElementAt(size_t x, size_t y, const char* inElem);
char* getElementAt(size_t x, size_t y) const;
//
size_t getHeight() const { return mHeight; }
size_t getWidth() const { return mWidth; }
static const size_t kDefaultWidth = 10;
static const size_t kDefaultHeight = 10;
protected:
void copyFrom(const Grid<char*>& src);
char*** mCells;
size_t mWidth, mHeight;
};
注意,在这个特例化中不要指定任何类型变量,例如T:现在在直接处理char*
。现在有一个明显的问题,那就是为什么这个类仍然是一个模板。也就是说,下面这个语法有什么意义?
template <>
class Grid<char*>
这个语法告诉编译器这个类是Grid类的char*
特例化版本。
假设没有使用这个语法,而是尝试写这样的代码:
class Grid
编译器不允许这样做,因为已经有一个名为Grid的类(原始的模板类)。只能通过特例化重用这个名称。
特例化的主要好处就是可以对用户隐藏。
当一个用户创建一个int或SpreadsheetCell的Grid时,编译器从原始的Grid模板生成代码。当用户创建一个char*
的Grid时,编译器会使用char*
的特例化版本。这些全部都在后台自动发生。
int main()
{
Grid<int> myIntGrid; // Uses original Grid template
Grid<char*> stringGrid1(2, 2); // Uses char* specialization
string dummy = "dummy";
stringGrid1.setElementAt(0, 0, "hello");
stringGrid1.setElementAt(0, 1, dummy.c_str());
stringGrid1.setElementAt(1, 0, dummy.c_str());
stringGrid1.setElementAt(1, 1, "there");
Grid<char*> stringGrid2(stringGrid1);
cout << stringGrid2.getElementAt(0, 1) << endl;
return 0;
}
特例化一个模板时,并没有“继承”任何代码:特例化和子类化不同。必须重新编写类的整个实现。不要求提供具有相同名称或行为的方法。事实上,您可以编写一个和原来的类没有任何关系完全不同的类。当然,这样做是滥用了模板特例化的能力,如果没有正当理由不应该这样做。
下面是char*
特例化版本的方法的实现。与模板定义不同,不必在每个方法或静态成员定义之前重复template<>语法:
template <>
class Grid<char*>
{
public:
Grid(size_t inWidth = kDefaultWidth, size_t inHeight = kDefaultHeight);
Grid(const Grid<char*>& src);
virtual ~Grid();
Grid<char*>& operator=(const Grid<char*>& rhs);
//
void setElementAt(size_t x, size_t y, const char* inElem);
char* getElementAt(size_t x, size_t y) const;
//
size_t getHeight() const { return mHeight; }
size_t getWidth() const { return mWidth; }
static const size_t kDefaultWidth = 10;
static const size_t kDefaultHeight = 10;
protected:
void copyFrom(const Grid<char*>& src);
char*** mCells;
size_t mWidth, mHeight;
};
Grid<char*>::Grid(size_t inWidth, size_t inHeight) :
mWidth(inWidth), mHeight(inHeight)
{
mCells = new char** [mWidth];
for (size_t i = 0; i < mWidth; i++) {
mCells[i] = new char* [mHeight];
for (size_t j = 0; j < mHeight; j++) {
mCells[i][j] = nullptr;
}
}
}
Grid<char*>::Grid(const Grid<char*>& src)
{
copyFrom(src);
}
Grid<char*>::~Grid()
{
// free the old memory
for (size_t i = 0; i < mWidth; i++) {
for (size_t j = 0; j < mHeight; j++) {
delete [] mCells[i][j];
}
delete [] mCells[i];
}
delete [] mCells;
mCells = nullptr;
}
void Grid<char*>::copyFrom(const Grid<char*>& src)
{
mWidth = src.mWidth;
mHeight = src.mHeight;
mCells = new char** [mWidth];
for (size_t i = 0; i < mWidth; i++) {
mCells[i] = new char* [mHeight];
}
for (size_t i = 0; i < mWidth; i++) {
for (size_t j = 0; j < mHeight; j++) {
if (src.mCells[i][j] == nullptr) {
mCells[i][j] = nullptr;
} else {
size_t len = strlen(src.mCells[i][j]) + 1;
mCells[i][j] = new char[len];
strncpy(mCells[i][j], src.mCells[i][j], len);
}
}
}
}
Grid<char*>& Grid<char*>::operator=(const Grid<char*>& rhs)
{
// check for self-assignment
if (this == &rhs) {
return *this;
}
// free the old memory
for (size_t i = 0; i < mWidth; i++) {
for (size_t j = 0; j < mHeight; j++) {
delete [] mCells[i][j];
}
delete [] mCells[i];
}
delete [] mCells;
mCells = nullptr;
// copy the new memory
copyFrom(rhs);
return *this;
}
void Grid<char*>::setElementAt(size_t x, size_t y, const char* inElem)
{
delete [] mCells[x][y];
if (inElem == nullptr) {
mCells[x][y] = nullptr;
} else {
size_t len = strlen(inElem) + 1;
mCells[x][y] = new char[len];
strncpy(mCells[x][y], inElem, len);
}
}
char* Grid<char*>::getElementAt(size_t x, size_t y) const
{
if (mCells[x][y] == nullptr) {
return nullptr;
}
size_t len = strlen(mCells[x][y]) + 1;
char* ret = new char[len];
strncpy(ret, mCells[x][y], len);
return ret;
}
getElementAt()
返回字符串的深度副本,因此不需要返回const char*
的重载。然而,由于返回了深度副本,所以调用者有责任通过delete[]
释放getElementAt()
返回的内存。
本节讨论了如何使用模板类特例化。通过这项特性,当模板类型被特定类型替换的时候,可以为一个模板编写特殊的实现。下一章继续讨论特例化,讨论的是一项称为部分特例化的更高级的特性。