引言:我们几乎都知道泛型编程会导致代码膨胀问题,那膨胀了就不好吗?当然啦,不仅仅是因为那一点点硬盘空间,因为同等的功能,目标文件或可执行文件太大,意味着包含着更多的指令,这样就会使指令的高速缓存压力山大,当然影响程序的执行效率。那么让我们看下面一个具体的例子:
假如你想为固定尺寸的正方矩阵编写一个template,并且该矩阵支持求逆矩阵计算:
//template支持n * n矩阵,元素类型是T
//size_t我们称之为非类型参数
template<typename T, size_t n>
class SquareMatrix
{
public:
//求逆矩阵
void invert()
{
cout << "求逆矩阵..." << endl;
}
};
那么,使用时可以这样:
SquareMatrix<double, 5> sm1;
sm1.invert();
SquareMatrix<double, 10> sm2;
sm2.invert();
上面实现了两个方阵,一个是5*5的,一个是10*10的,但是除了大小不同外,函数的实现逻辑完全相同,相当于有两份invert()实现逻辑被生成,
这是个典型的template编程导致的代码膨胀的例子。那么怎么做呢?很自然的想到,把那个大小变成参数不就好了吗?于是有下面的代码:
//于尺寸无关的base class,用于正方矩阵
template<typename T>
class SquareMatrixBase
{
public:
void invert(size_t matrixSize)
{
cout << "求逆矩阵..." << endl;
}
};
template<typename T, size_t n>
class SquareMatrix : private SquareMatrixBase<T>
{
private:
//避免遮掩base版的invert
using SquareMatrixBase<T>::invert;
public:
void invert()
{
//inline调用base版的invert
this->invert(n);
}
};
从上面可以看出,带参数的invert在base class中,不过它本身是个template,但是只对矩阵的元素类型参数化,所以对于给定的元素对象类型(比如double),所有
矩阵只是共享一个也是唯一一个base class,所以它们也将共享一个class内的invert。
注意这里还有两个知识点:
1.使用using声明和this->指针(请参考我的43条笔记);
2.这里的private继承只是为了帮助derived class的实现,并不是为了表现SquareMatrix和SquareMatrixBase之间的is-a关系,所以证明private是个实现继承(详见E39条)
但是这样就够了吗?问题还有,那就是SquareMatrixBase::invert如何知道该操作什么数据呢?数据放在哪里呢?那当然添加成员变量来搞定,也是最终是实现:
#include <string>
#include <iostream>
#include <memory>
using namespace std;
//于尺寸无关的base class,用于正方矩阵
template<typename T>
class SquareMatrixBase
{
public:
SquareMatrixBase(size_t n, T *pMem)
: size(n), pData(pMem)
{
}
void setDataPtr(T *ptr)
{
pData = ptr;
}
void invert()
{
//运用size和pData求逆矩阵
//...
cout << "求逆矩阵..." << endl;
}
private:
size_t size;
T *pData;
};
template<typename T, size_t n>
class SquareMatrix : private SquareMatrixBase<T>
{
public:
SquareMatrix()
: SquareMatrixBase<T>(n, 0), pData(new T[n * n])
{
this->setDataPtr(pData.get());
}
void invert()
{
//inline调用base版的invert
SquareMatrixBase<T>::invert();
}
private:
unique_ptr<T[]> pData;
};
int _tmain(int argc,TCHAR* argv[])
{
SquareMatrix<double, 5> sm1;
sm1.invert();
SquareMatrix<double, 10> sm2;
sm2.invert();
return 0;
}
其他描述:这个条款只讨论非类型模板参数带来的膨胀,其实类型参数也会导致膨胀。许多平台上的int和long有相同的二进制表示,vector<int>和vector<long>
的成员函数有可能完全相同,这正是膨胀的最佳定义。还有但凡templates持有指针者(例如list<int *>,list<const int *>,list<SquareMatrix<long, 3>*>等等),往往
应该对每一个成员函数使用唯一一份底层实现,如果你实现某些成员函数而它们操作强型指针T*,你应该令它们调用另一个操作无类型指针void*的函数,有后者完成实际工作,某些C++标准库如,vector,deque,list等templates就是这样实现的。
总结:
1.Templates生成多个classes和多个函数,所以任何template代码都不该与某个造成膨胀的template参数产生相依关系;
2.因非类型参数而造成的代码膨胀,往往可以消除,做法是以函数参数或class成员变量替换template参数;
3.因类型参数而造成的代码膨胀,往往可以降低,做法是让带有完全相同的二进制表述的具现类型共享实现码。