首页 > 代码库 > Effective C++:条款44:将与参数无关的代码抽离template

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

(一)

    template是节省时间和避免重复代码的一个奇妙方法。class template的成员函数只有在被使用时才被暗中具现化。function templates有类似的诉求。

    但是如果你不小心,使用templates可能导致代码膨胀(code bloat):其二进制代码带着重复(或几乎重复)的代码、数据、或两者。其结果可能源码看起来合身整齐,但目标码却不是那么回事。你需要知道如何避免这样的二进制浮夸。

    主要工具是:共性与变性分析。

    在non-template中,重复十分明确,然而在template中,重复是隐晦的:你必须训练自己去感受当template被具现化多次时可能发生的重复。

    举例,矩阵template,支持矩阵inversion运算

template<typename T, std::size_t n> 
class SquareMatrix{ 
public: 
    void invert(); 
};

template接受一个类型参数T,还有一个类型为size_t的非类型参数(non-type parameter)。

现在考虑,以下情况:

SquareMatrix<double, 5> sm1; 
sm1.invert(); 
SquareMatrix<double, 10> sm2; 
sm2.invert();
这会具现两份invert。这些函数并非完全相同,但除了常量5和10,其他部分都相同,这是template引出代码膨胀的一个典型例子。

所以我们要对它进行修改!

(二)

我们先把它修改成如下形式:

template<typename T>   //与尺寸无关的base class
class SquareMatrixBase {
protected:
	void invert(size_t matrixSize);  //以给定的尺寸求逆矩阵
};

template<typename T, size_t n>
class SquareMatrix : private SquareMatrixBase<T> {
private:
	using SquareMatrixBase<T>::invert;  //避免遮掩base版的invert
public:
	void invert() {
		this->invert(n);  //制造一个inline调用,用this->为了不被derived classes的函数名称掩盖
	}
};
带参数的invert位于base class中。和SquareMatrix一样,也是个template,不同的是他只对“矩阵元素对象的类型”参数化,不对矩阵的尺寸参数化。因此对于给定的元素对象类型,所有矩阵共享同一个(也是唯一一个)SquareMatrixBase class。也将因此而共享这唯一一个class内的invert。

目前为止一切都好,但是SquareMatrixBase::invert如何知道该操作什么数据?想必只有derived class知道。一个可能的做法是为SquareMatrixBase::invert添加一个新的参数,也许是个指针,指向一块用来放置矩阵数据的内存起始点。那行的通,但是十之八九invert不是唯一一个可写为“形式与尺寸无关并可移至SquareMatrixBase内”的SquareMatrix函数。如果有若干这样的函数,我们可以对所有这样的函数添加一个额外参数,却得一次次地告诉SquareMatrixBase相同的信息,这样不好。

(三)

我们尝试另外一种办法:

令SquareMatrixBase贮存一个指针,指向矩阵数值所在的内存。而只要它存储了那些东西,也就可能存储矩阵尺寸:

这允许derived classes决定内存分配方式。某些实现版本也许会将矩阵数据存储在SquareMatrix对象内部:

template<typename T>
class SquareMatrixBase {
protected:
	SquareMatrixBase(size_t n, T* pMem) : size(n), pData(pMem) {}
	void setDataPtr(T* ptr) {
		pData = http://www.mamicode.com/ptr;>
还有一种改法:

template<typename T>
class SquareMatrixBase {
protected:
	SquareMatrixBase(size_t n, T* pMem) : size(n), pData(pMem) {}
	void setDataPtr(T* ptr) {
		pData = http://www.mamicode.com/ptr;>以上这种做法是把每个矩阵的数据放进heap。


请记住:

(1)Template生成多个classes和多个函数,所以任何template代码都不该与某个造成膨胀的template参数产生相依关系。

(2)因非类型模版参数而造成的代码膨胀,往往可消除,做法是以函数参数或class成员变量替换template参数。

(3)因类型参数而造成的代码膨胀,往往可降低,做法是让带有完全相同二进制表述的具现类型共享实现码。




















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