编写template时,为避免代码重复,可将相同的代码抽离包装,再放回原有的template中,这其中有个敲门。在template中,重复是隐晦的,毕竟只存在一份template源码,所以你必须训练自己去感受当template被实例化多次时可能发生的重复。
举个例子,假设你想为固定尺寸的正方矩阵编写一个template。该矩阵的性质之一时支持逆矩阵运算。
template <typename T, std::size_t n>
class SquareMatrix {
public:
...
void invert();
};
现在,考虑这些代码:
SquareMatrix<double, 5> sm1;
...
sm1.invert();
SquareMatrix<double, 10> sm2;
...
sm2.invert();
这会实例化两份invert,除了常量5和10,这两个函数的其他部分完全相同。
你会为它们建议一个带参数的函数,然后以5和10来调用这个函数,而不重复代码。下面是对SquareMatrix的第一次修改:
template <typename T>
class SquareMatrixBase {
protected:
...
void invert(std::size_t matrixSize);
...
};
template <typename T, std::size_t n>
class SquareMatrixBase<T> : private SquareMatrixBase<T> { // 条款39
private:
using SquareMatrixBase<T>::invert(); // 条款43
public:
...
void invert() { this->invert(n); }
};
SquareMatrixBase::invert
如何知道该操作什么数据?最好的办法是令SquareMatrixBase存储一个指针,指向矩阵数值所在的内存。成果看起来像这样:
template <typename T>
class SquareMatrixBase {
protected:
SquareMatrixBase(std::size_t n, T* pMem)
: size(n), pData(pMem) {}
void setDataPtr(T* ptr) { pData = ptr; }
...
private:
std::size_t size; // 矩阵大小
T* pData;
};
这允许派生类决定内存分配方式。某些实现版本也许会决定将矩阵数据存储在SquareMatrix对象内部:
template <typename T, std::size_t n>
class SquareMatrix : private SquareMatrixBase<T> {
public:
SquareMatrix() : SquareMatrixBase<T>(n, data) {}
...
private:
T data[n * n];
};
这种类型对象不需要动态分配内存,但对象自身可能非常大。另一种做法是把每个矩阵的数据放进heap:
template <typename T, std::size_t n>
class SquareMatrix : private SquareMatrixBase<T> {
public:
SquareMatrix()
: SquareMatrixBase<T>(n, 0), pData(new T[n*n])
{ this->setDataPtr(pData.get()); }
...
private:
boost::scoped_array<T> pData;
};
请记住
- template生成多个class和多个函数,所以任何template代码都不该与某个造成膨胀的template参数产生相依关系。
- 因非类型模板参数而造成的代码膨胀,往往可以消除。做法是以函数参数或class成员变量替换template参数。
- 因类型参数而造成的代码碰撞,往往可以降低,做法是让带有完全相同二进制表述的具体类型实现共享代码。