条款44:将与参数无关的代码抽离模板
1.1 提出问题
使用模板可能会导致代码膨胀:二进制文件中有重复(或几乎重复)的代码或数据。
template<typename T, std::size_t n> // nxn个T类型对象矩阵的模板,n为非类型参数
class SquareMatrix {
public:
...
void invert(); // 对矩阵求逆
};
//这里将实例化两个invert副本
SquareMatrix<double, 5> sm1;
...
sm1.invert(); // 调用 SquareMatrix<double, 5>::invert
SquareMatrix<double, 10> sm2;
...
sm2.invert(); // 调用 SquareMatrix<double, 10>::invert
1.2 解决办法
为解决重复问题,下面是我们的第一次尝试:
template<typename T> // 与大小无关的方阵基类
class SquareMatrixBase {
protected:
...
void invert(std::size_t matrixSize); // 求给定大小的逆矩阵
...
};
template< typename T, std::size_t n>
class SquareMatrix : private SquareMatrixBase<T> {
private:
using SquareMatrixBase<T>::invert; // 避免隐藏invert的基类版本
public:
...
void invert() { this->invert(n); } // 内联调用基类版本的invert
};
SquareMatrixBase::invert通过参数知道矩阵的大小,但它如何知道特定矩阵的数据在哪里呢?如果还是通过函数的参数传入指向数据的指针,将可能需要在不同的函数里重复传入相同的指针。所以我们决定把数据的(连同大小)都存在基类:
template<typename T>
class SquareMatrixBase {
protected:
SquareMatrixBase(std::size_t n, T* pMem) //存储矩阵大小和数据的指针
: size(n), pData(pMem) {}
void setDataPtr(T* ptr) { pData = ptr; } // 为pData赋值
...
private:
std::size_t size; // 矩阵大小
T* pData; // 指向矩阵数据的指针
};
template<typename T, std::size_t n>
class SquareMatrix : private SquareMatrixBase<T> {
public:
SquareMatrix()
: SquareMatrixBase<T>(n, data) // 将矩阵大小和数据的指针传送给基类
{}
...
private:
T data[n * n];
};
1.3 其他办法
另一种方法是将每个矩阵的数据放到堆中:
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:
std::unique_ptr<T> pData;
};
1.4 总结
- 模板会生成多个类和多个函数,因此任何不依赖于模板参数的模板代码都会导致代码膨胀。
- 通过将模板参数替换为函数参数或类数据成员,通常可以消除由非类型模板参数引起的膨胀。
- 通过共享具有相同二进制表示方式的类型的实例化,可以减少类型参数导致的膨胀。