C++模板编程


674 浏览 5 years, 4 months

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()返回的内存。

本节讨论了如何使用模板类特例化。通过这项特性,当模板类型被特定类型替换的时候,可以为一个模板编写特殊的实现。下一章继续讨论特例化,讨论的是一项称为部分特例化的更高级的特性。