Effective C++ 44条 将与参数无关的代码抽离templates

引言:我们几乎都知道泛型编程会导致代码膨胀问题,那膨胀了就不好吗?当然啦,不仅仅是因为那一点点硬盘空间,因为同等的功能,目标文件或可执行文件太大,意味着包含着更多的指令,这样就会使指令的高速缓存压力山大,当然影响程序的执行效率。那么让我们看下面一个具体的例子:

假如你想为固定尺寸的正方矩阵编写一个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.因类型参数而造成的代码膨胀,往往可以降低,做法是让带有完全相同的二进制表述的具现类型共享实现码。




评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值