[C++ Template]模板与设计--表达式模板

目录

第18章 表达式模板

18.1 临时变量和分割循环

18.2 在模板实参中编码表达式

18.2.1 表达式模板的操作数

18.2.2 Array类型

18.2.3 运算符

18.2.4 回顾

18.2.5 表达式模板赋值

18.3 表达式模板的性能与约束


第18章 表达式模板

在这一章里, 我们将介绍一种称为表达式模板(expressiontemplate) 的编程技术。 刚开始, 是为了支持一种数值数组的类而引入该技术的。 因此, 在这一章里, 我们把数值数组作为讨论表达式模板的着眼点。

对于一个数值数组类, 它需要为基于整个数组对象的数值操作提供支持。 例如, 我们可能需要对两个数组进行求和, 最后结果所含的每个元素是两个实参数组中对应元素值之和。 类似地, 我们也可以对整个数组进行放大(即我们后面所指的scalar) , 也就是说数组中的每个元素都乘以一个大于1的值。 通常而言, 我们期望可以像内建类型一样, 让数组也具有这样的放大(scalar) 运算符:

Array<double> x(1000), y(1000);
…
x=1.2*x + x*y;

谈到表达式模板, 我们自然就会想起前面的template metaprogramming。 之所以会有这样的联系, 一方面是由于: 表达式模板有时依赖于深层的嵌套模板实例化, 而这种实例化又和我们在template metaprogramming中遇到的递归实例化非常相似(见17.7节的例子) ; 另一方面则是由于: 最初开发这两种实例化技术都是为了支持高性能的数组操作, 而这又从另一个侧面说明了metaprogramming和表达式模板是息息相关的。 当然, 这两种技术还是互补的。 例如,metaprogramming 主要用于小的、 大小固定的数组, 而表达式模板则适用于能够在运行期确定大小、 中等大小的数组。

 

18.1 临时变量和分割循环

在深入了解表达式模板之前, 让我们先来看一种比较简单的 、 用于实现数值数组操作的模板实现。 其中基本的数组模板看起来如下所示(SArray的含义是simple array) :

template<typename T>
class SArray 
{
public:
	// 创建一个具有初始值大小的数组
	explicit SArray(size_t s)
		: storage(new T[s]), storage_size(s) {
		init();
	} 
	//拷贝构造函数
	SArray(SArray<T> const& orig)
		: storage(new T[orig.size()]), storage_size(orig.size()) {
		copy(orig);
	} 
	//析构函数: 释放内存空间
	~SArray() {
		delete[] storage;
	} 

	// 赋值运算符
	SArray<T>& operator= (SArray<T> const& orig)
	{
		if (&orig != this) {
			copy(orig);
		}
		return *this;
	}
	//返回数组大小
	size_t size() const 
	{
		return storage_size;
	} 
	//针对常数和变量的下标运算符
	T operator[] (size_t idx) const 
	{
		return storage[idx];
	} 

	T& operator[] (size_t idx) 
	{
		return storage[idx];
	}
protected:
	// 运用缺省构造函数来初始化值
	void init() 
	{
		for (size_t idx = 0; idx < size(); ++idx) 
		{
			storage[idx] = T();
		}
	}
	//拷贝另一个数组的值
	void copy(SArray<T> const& orig) 
	{
		assert(size() == orig.size());
		for (size_t idx = 0; idx < size(); ++idx) 
		{
			storage[idx] = orig.storage[idx];
		}
	}
private:
	T* storage; // 元素的存储空间
	size_t storage_size; // 元素的个数
};

//而数值运算符可以编码如下:
// exprtmpl/sarrayops1.hpp
// 对两个SArrays求和
template<typename T>
SArray<T> operator+ (SArray<T> const& a, SArray<T> const& b)
{
	SArray<T> result(a.size());
	for (size_t k = 0; k < a.size(); ++k) {
		result[k] = a[k] + b[k];
	} 
	return result;
} 
//对两个SArray求积
template<typename T>
SArray<T> operator* (SArray<T> const& a, SArray<T> const& b){
	SArray<T> result(a.size());
	for (size_t k = 0; k < a.size(); ++k) {
		result[k] = a[k] * b[k];
	} 
	return result;
} 
//让一个SArray 乘以一个放大倍数
template<typename T>
SArray<T> operator* (T const& s, SArray<T> const& a)
{
	SArray<T> result(a.size());
	for (size_t k = 0; k < a.size(); ++k) {
		result[k] = s*a[k];
	} 
	return result;
} 
//对SArray和scalar求积
// 对scalar 和 SArray求和
// 对SArray 和 scalar 求和
…

我们还可以写出其他的一些版本, 也可以类似地添加其他的一些运算符。 但为了简单起见, 上面的这些运算符已经足够考察下面的例子表达式了:

int main()
{
    SArray<double> x(1000), y(1000);…
    x = 1.2*x + x*y;
}

显然, 上面的实现是非常低效的, 其原因主要是以下两方面:

1.每个运算符操作(除了赋值运算符) 至少需要生成了一个临时数组(也就是说, 在我们的例子中, 即使编译器不执行任何附加的临时拷贝操作, 也至少会生成3个大小为1 000的临时数组) 。

2.运算符程序的每次使用都要求对实参和结果数组进行额外的遍历(这就是说, 在我们的例子中, 即使只是生成了一个SArray对象, 大概也要读取6 000次double值, 写入4 000次double值) 。

让我们通过下面运用临时变量的表达式, 具体地分析上面的这些结论:

tmp1 = 1.2*x; // 循环1 000次子操作(即元素操作) , 再加上创建和删除tmp1
tmp2 = x*y // 循环1 000次子操作, 再加上创建和删除tmp2
tmp3 = tmp1+tmp2; // 循环1 000次子读操作、 1 000次写操作,再加上生成和删除tmp3
x = tmp3; // 1 000次读操作和1 000次写操作

对于元素个数少的数组而言, 除非能够分配非常快速的内存配置器, 否则创建多余临时对象的过程通常都会占用每个操作的大部分时间; 而对于元素个数很多的数组而言, 则是完全不允许生成临时对象的, 因为根本就没有足够的内存来容纳这些临时对象。

实际上, 每个数值数组程序库的实现都会面临这个问题, 因此通常鼓励我们多使用包含计算的赋值运算符(computed assignments, 诸如+=、 *=等) , 来代替前面纯粹的赋值运算符。 使用包含计算的赋值运算符的好处在于: 由于实参和结果都是由调用者提供, 因此将不需要创建任何临时对象。 例如, 我们可以这样添加SArray成员:

// SArray的自加运算符
template<class T>
SArray<T>& SArray<T>::operator+= (SArray<T> const& b)
{
	for (size_t k = 0; k < size(); ++k) {
		(*this)[k] += b[k];
	} 
	return *this;
} 
//SArray的自乘运算符
template<class T>
SArray<T>& SArray<T>::operator*= (SArray<T> const& b)
{
	for (size_t k = 0; k < size(); ++k) {
		(*this)[k] *= b[k];
	} 
	return *this;
} 
//针对放大倍数的自乘运算符
templat
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值