expression template

写在前面的话:模板充斥着无限的可能性

 

表达式模板metaprogramming:metaprogramming主要用于大小固定的、小的数组,表达式模板适用于能够在运行期确定大小、中等大小的数组(关于metaprogramming,在我的文章《metaprogram(元编程初探)》)

 

一个传统的数值数组操作程序:

Traits头文件:

#ifndef TRAIT
#define TRAIT
#include<string>
using namespace std;

template<typename T>
class Traits{
public:
	typedef T Type;
	static Type zero(){
		return T();
	}
};



//char
template<>
class Traits<char>{
public:
	typedef char Type;
	static Type zero(){
		return ' ';
	}
};

//short
template<>
class Traits<short>{
public:
	typedef short Type;
	static Type zero(){
		return 0;
	}
};

//int
template<>
class Traits<int>{
public:
	typedef int Type;
	static Type zero(){
		return 0;
	}
};

//double
template<>
class Traits<double>{
public:
	typedef double Type;
	static Type zero(){
		return 0;
	}
};

//string
template<>
class Traits<string>{
public:
	typedef string Type;
	static Type zero(){
		return "";
	}
};

#endif

S_Array头文件:

#ifndef SARRAY
#define SARRAY

#include<stddef.h>
#include<cassert>
#include"Traits.h"

template<typename T>
class SArray{
public:
	//创建一个s大小的数组
	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;
	}

	//针对常量和变量的subscribe运算符1
	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] = Traits<T>::zero();//这里使用了一个可以产生正确T类型初值的Trait
		}
	}

	//拷贝另一个数组的值
	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;
};

#endif

S_ArrayOprs头文件:

//此头文件中定义关于SArray模板的操作符
#ifndef SARRAYOPRS
#define SARRAYOPRS
#include"Sarray.h"

//operator+
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;
}

//operator*
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;
}

//让一个放大倍数乘以放大倍数
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] = a[k] *s;
	}
	return result;
}


//。。。。。。

#endif

测试源文件:

#include<iostream>
#include"Sarray.h"
#include"SArrayOprs.h"

using namespace std;

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

 

对于"x = 1.2*x + x*y;"的效率分析:

1)每个运算符操作至少需要生成一个临时数组,因此上面的表达式至少会生成三个临时的大小为1000的数组

2)运算符程序的每次使用都要求对实参和结果数组进行额外的遍历,上面的表达式中,进行了6000次的读取double值,写入4000次的double值

 

可以看到上述的示例程序效率非常低下,实际上,每个数值数组程序库的实现都面临这个问题,因此通常鼓励我们多使用包含计算的赋值运算符:

 

Sarray头文件(改动处):

//计算的赋值运算符
//+=
SArray<T> & operator += (SArray<T> const & b);
//*=
SArray<T> & operator*=(SArray<T> const & b);
//*=
SArray<T> & operator*=(T const & s);

ASrrayOprs头文件(改动处):

//operator+=
template<typename T>
SArray<T> & SArray<T>::operator += (SArray<T> const & b){
	for (size_t k = 0; k < size(); k++){
		(*this)[k] +=b[k];
	}
	return *this;
}

//operator*=
template<typename T>
SArray<T> & SArray<T>::operator*=(SArray<T> const & b){
	for (size_t k = 0; k < size(); ++k){
		(*this)[k] *= b[k];
	}
	return *this;
}

//operator*=
template<typename T>
SArray<T> & SArray<T>::operator*=(T const & s){
	for (size_t k = 0; k < size(); ++k){
		(*this)[k] *= s;
	}
	return *this;
}

测试源文件:

#include<iostream>
#include"Sarray.h"
#include"SArrayOprs.h"

using namespace std;

void main(){
	SArray<double> x(1000), y(1000);
	//x = 1.2*x + x*y;

	//使用含有计算的赋值运算
	SArray<double> tmp(x);
	tmp *= y;
	x *= 1.2;
	x += tmp;
}

但是这仅仅减少了两个临时数组的创建,并没有解决问题,而且代码看起来很不雅观!

 

 

在模板实参中编码表达式——记录

思路:在进行求值之前,我们只是记录每一个对象和应用到该对象的每个操作(重点是仅仅是记录而不计算),并且这些操作在编译器已经是确定了的!

因此,对于表达式:    z = (double)2*x+x*y     其中x*y我们只是建立一个表示——x的每个元素乘以y相应的元素,在最后真正赋值的时候(赋值给z),才会实现这些表示!

综合上述观点,我们需要实现类似这种策略:

for (int inx = 0; idx < x.size();++idx){
	x[idx] = 1.2*x[idx] + x[idx] * y[idx];
}

此策略不需要任何的局部数组,并且只需要大约3000次的内存读取和1000次的内存写入操作!

 

(以下注意有“表示”的地方)

A_Add文件——用来表示两个容器的相加:

#ifndef A_ADD
#define A_ADD
#include<stddef.h>
#include<cassert>
#include"A_Traits.h"

template<typename T,typename OP1,typename OP2>
class A_Add{
private:

	//内部保存元素的形式是保存引用还是保存值
	typename A_Traits<OP1>::ExprRef op1;
	typename A_Traits<OP2>::ExprRef op2;

public:

	A_Add(OP1 const & a,OP2 const & b):op1(a),op2(b){}

	T operator[](size_t idx)const{
		return op1[idx] + op2[idx];
	}

	size_t size()const{
		assert(op1.size() == 0 || op2.size() == 0 || op1.size() == op2.size());
			return op1.size() != 0 ? op1.size() : op2.size();
	}
};
#endif

A_Mult文件——用来表示两个容器的相乘:

#ifndef AMULT
#define AMULT
#include<stddef.h>
#include<cassert>
#include"A_Traits.h"
template<typename T, typename OP1, typename OP2>
class  A_Mult{
private:
	//内部保存元素的形式是保存引用还是保存值
	typename A_Traits<OP1>::ExprRef op1;
	typename A_Traits<OP2>::ExprRef op2;
public:
	A_Mult(OP1 const & a, OP2 const & b) :op1(a), op2(b){}
	T operator[](size_t idx)const{
		return op1[idx] * op2[idx];
	}
	size_t size()const{
		assert(op1.size() == 0 || op2.size() == 0 || op1.size() == op2.size());
		return op1.size()!=0?op1.size():op2.size();
	}
};
#endif

A_Scalar文件——用来将scalar打包成伪容器:

#ifndef A_SCALAR
#define A_SCALAR
#include<stddef.h>
#include<cassert>
#include"A_Traits.h"

template<typename T>
class A_Scalar{
private:
	T const & s;
public:
	A_Scalar(T const & v):s(v){}

	T operator[](size_t)const{
		return s;
	}

	size_t size()const{
		return 0;
	}
};

#endif

A_Traits文件——是否值保存还是引用保存的Traits模板:

#ifndef ATRAITS
#define ATRAITS
#include"A_Scalar.h"

template<typename T>
class A_Traits{
public:
	typedef T const & ExprRef;//寻常的类型都是以传递引用的形式传递参数
};

//对于A_Scalar,以引用的形式传递
template<typename T>
class A_Traits<A_Scalar<T>>{
public:
	typedef A_Scalar<T> ExprRef;//寻常的类型都是以传递引用的形式传递参数
};
#endif

Traits文件——关于一个类型的初始值的Trait模板:

#ifndef TRAIT
#define TRAIT
#include<string>
using namespace std;

template<typename T>
class Traits{
public:
	typedef T Type;
	static Type zero(){
		return T();
	}
};

//char
template<>
class Traits<char>{
public:
	typedef char Type;
	static Type zero(){
		return ' ';
	}
};

//short
template<>
class Traits<short>{
public:
	typedef short Type;
	static Type zero(){
		return 0;
	}
};

//int
template<>
class Traits<int>{
public:
	typedef int Type;
	static Type zero(){
		return 0;
	}
};

//double
template<>
class Traits<double>{
public:
	typedef double Type;
	static Type zero(){
		return 0;
	}
};

//string
template<>
class Traits<string>{
public:
	typedef string Type;
	static Type zero(){
		return "";
	}
};

#endif

S_Array文件——真实容器(真实内存):

#ifndef SARRAY
#define SARRAY
#include<stddef.h>
#include<cassert>
#include"Traits.h"

template<typename T>
class SArray{
public:
	//创建一个s大小的数组
	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;
	}

	//针对常量和变量的subscribe运算符1
	T operator[](size_t idx) const {
		return storage[idx];
	}
	T& operator[](size_t idx){
		return storage[idx];
	}

	void FillValue(T const & value){
		for (size_t idx = 0; idx < size(); idx++){
			storage[idx] = value;
		}
	}
	计算的赋值运算符
	+=
	//SArray<T> & operator += (SArray<T> const & b);
	*=
	//SArray<T> & operator*=(SArray<T> const & b);
	*=
	//SArray<T> & operator*=(T const & s);
protected:

	//初始化
	void init(){
		for (size_t idx = 0; idx < size();idx++){
			storage[idx] = Traits<T>::zero();
		}
	}

	//拷贝另一个数组的值
	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;
};

#endif

Array文件——封装了SArray或(和SArray有关联)的一个集合:

#ifndef ARRAY
#define ARRAY
#include<stddef.h>
#include<cassert>
#include"S_Array.h"

template<typename T,typename Rep=SArray<T>>
class Array{
private:
	Rep expr_rep;
public:
	Array(size_t s)
		:expr_rep(s){
	}

	Array(Rep const & rb) :expr_rep(rb){
	}

	//填充每一个元素的值
	void FillValue(T const & value){
		expr_rep.FillValue(value);
	}

	//相同类型Array之间赋值
	Array & operator=(Array const & b){
		assert(size()==b.size());
		for (size_t idx = 0; idx < b.size();idx++){
			expr_rep[idx] = b[idx];
		}
		return *this;
	}

	//不同类型Array之间赋值
	template<typename T2,typename Req2>
	Array & operator=(Array<T2,Req2> const & b){
		assert(size() == b.size());
		for (size_t idx = 0; idx < b.size(); idx++){
			expr_rep[idx] = b[idx];
		}
		return *this;
	}

	//size()操作
	size_t size()const{
		return expr_rep.size();
	}
	
	//subscript操作——const版本
	T operator[](size_t idx)const{
		assert(idx<size());
		return expr_rep[idx];
	}

	//subscript操作——非const版本
	T & operator[](size_t idx){
		assert(idx<size());
		return expr_rep[idx];
	}

	//得到内部数组——const版本
	Rep const & rep()const{
		return expr_rep;
	}

	//得到内部数组——非const版本
	Rep & rep(){
		return expr_rep;
	}
};

#endif

ArrayOprs文件——操作符实现文件:

#ifndef ARRAYOPRS
#define ARRAYOPRS
#include"A_Add.h"
#include"A_Mult.h"
#include"A_Scalar.h"
#include"Array.h"

//两个数组相加
template<typename T,typename R1,typename R2>
Array<T,A_Add<T,R1,R2>>
operator+(Array<T,R1> const & a,Array<T,R2> const & b){
	return Array<T, A_Add<T, R1, R2>>
		(A_Add<T,R1,R2>(a.rep(),b.rep()));
}

//两个数组相乘
template<typename T, typename R1, typename R2>
Array<T, A_Mult<T, R1, R2>>
operator*(Array<T, R1> const & a, Array<T, R2> const & b){
	return Array<T, A_Mult<T, R1, R2>>
		(A_Mult<T, R1, R2>(a.rep(), b.rep()));
}

//scalar和数组相加
template<typename T,typename R2>
Array < T, A_Mult < T, A_Scalar<T>, R2 >>
operator*(T const & s, Array<T, R2> const & b){
	return Array<T, A_Mult<T, A_Scalar<T>, R2>>
		(A_Mult<T, A_Scalar<T>, R2>(A_Scalar<T>(s), b.rep()));
}

#endif

 

分析图:

 

其中比较重要(核心)的分析是:

此核心图亦即表明了我们的思路:用表达式表示,但是最后赋值的时候才实现这些表示

 

为了对我们的思路进一步说明,我们可以这样调用(下面代码会在Array文件中提示”&“错误——可以取消提示处的&来解决):

void main(){
	Array<double> x(1000),y(1000),z(1000);
	x.FillValue(2);
	y.FillValue(2);
	z = (double)2*x+x*y;
	int idx = 0;
	cout<<((double)2 * x + x*y)[idx];

	//for (size_t i = 0; i <z.size();i++){
	//	if (i %40 == 0)
	//		cout << endl;
	//	cout <<z[i] << " ";	
	//}
}

——只触发一次“表示”的实现(而赋值只是遍历实现“表示”,如下代码所示)

//不同类型Array之间赋值
template<typename T2,typename Req2>
Array & operator=(Array<T2,Req2> const & b){
	assert(size() == b.size());
	for (size_t idx = 0; idx < b.size(); idx++){
		expr_rep[idx] = b[idx];
	}
	return *this;
}

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

柔弱胜刚强.

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值