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 = ptr;
	}
private:
	size_t size;
	T* pData;
};

template<typename T, size_t n>
class SquareMatrix : private SquareMatrixBase<T> {
public:
	SquareMatrix() : SquareMatrixBase(n, data) {}
private:
	T data[n*n];
};

还有一种改法:

template<typename T>
class SquareMatrixBase {
protected:
	SquareMatrixBase(size_t n, T* pMem) : size(n), pData(pMem) {}
	void setDataPtr(T* ptr) {
		pData = ptr;
	}
private:
	size_t size;
	T* pData;
};

template<typename T, size_t n>
class SquareMatrix : private SquareMatrixBase<T> {
public:
	SquareMatrix() : SquareMatrixBase<T>(n, 0),  //base class的数据指针设为null
		pData(new T[n*n])   //为内容分配内存,将指向该内存的指针存储起来
	{
		this->setDatePtr(pDate.get()); //将pData的一个副本交给base class
	}
private:
	boost::scoped_array<T> pData;
};
以上这种 做法是把每个矩阵的数据放进heap。


请记住:

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

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

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




















  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值