表达式模板expression template

来源:互联网 发布:淘宝后台填写宝贝 编辑:程序博客网 时间:2024/06/01 09:18

原创作品,转载请注明版权信息。

这篇文章我们将学习如何把上篇文章中提高性能采用的Lazy Evaluation的技术推广到更加广义的应用。

相关代码请参阅:

http://en.wikipedia.org/wiki/Expression_templates

这部分代码对初学者是有一定的难度。我们一个一个类来学习提高:

第一个类:

template <typename E>
// A CRTP base class for Vecs with a size and indexing:
class VecExpression 
{
public:
typedef std::vector<double>         container_type;
typedef container_type::size_type   size_type;
typedef container_type::value_type  value_type;
typedef container_type::reference   reference;
//get the size
size_type size() const { return static_cast<E const&>(*this).size(); }
//access the element using []
value_type operator[](size_type i) const { return static_cast<E const&>(*this)[i]; }
//function call operator(), const variation for other type applications
operator E&() { return static_cast<E&>(*this); } //
operator E const&() const { return static_cast<const E&>(*this); }
};


这个类定义了表达式类,这是一个基类。其处理的类型为E,我们在此认为就是一个未知类型。

container_type为标准库的vector<double>,也就是说现在要处理vector类型的表达式问题。

成员函数:

size(): 我们调用的是E类型的size(), 因此在此作了一个强制类型转换。

operator E&(): 这定义了一个隐式的类型转换。这种定义类似于构造函数,没有返回类型,把当前的VecExpression类型转化为E&类型。这样当表达式中要把VecExpression(或者其派生类类型)转化为E类型(非常量),这个函数就被调用。

operator E const&() 同上,在需要常量类型时调用。

看来,这个类型主要是为了一个到类型E的转换中介。


第二个类:

// The actual Vec class:
class Vec : public VecExpression<Vec> 
{
container_type _data; //stl vector type
public:
//access vector elements
reference operator[](size_type i) { return _data[i]; }
value_type operator[](size_type i) const { return _data[i]; }
//access vector size
size_type size() const { return _data.size(); }
//constructor
Vec(size_type n) : _data(n) {} // Construct a given size:

// Construct from any VecExpression:
template <typename E>
Vec(VecExpression<E> const& vec) 
{
E const& v = vec;
_data.resize(v.size());//clear and resize, why we need this???it is already allocated
for (size_type i = 0; i != v.size(); ++i) 
{
_data[i] = v[i];
}
}


template <typename E>
//Vec(VecExpression<E> const& vec) 
Vec& operator=(VecExpression<E> const& vec)
{
E const& v = vec;
//_data.resize(v.size());//clear and resize, why we need this???it is already allocated
for (size_type i = 0; i != v.size(); ++i) 
{
_data[i] = v[i];
}
return (*this);
}
};

类型Vec继承上面的VecExpression,同时具体化类未知类型E类型Vec,也就是说,上面的隐式类型转换要转换到Vec类型。

1。定义了一个std::vector<double>的成员对象,也就是这个是我们要处理的数据。

2。operator[] 用于存取Vec对象里的元素。为什么需要两个?reference_type返回引用,可以用于左边或右边,value_type返回的值,只能用于右边。现在我还不清楚为什么需要这个值类型。

3。构造函数Vec(size_type n)调用std::vector的构造函数用于分配给定长度的向量空间。

4。复制构造函数。注意它是用一个VecExpression<E>类型来构造Vec类型。这个好像有点难以理解。其表达的其实正是我们问题的核心:把一个向量表达式类型构造一个向量,比如:Vec c=a+b; (a,b 均为Vec类型,a*b为VecExpression<Vec>类型,也就是向量表达式类型。

5。赋值运算符,这个运算符是我加的,为的是支持Vec c; c=a+b; 这种表达式。


第三个类:

//vector addition 
template <typename E1, typename E2>
class VecAdd : public VecExpression<VecAdd<E1, E2> > 
{
E1 const& _u;
E2 const& _v;
public:
typedef Vec::size_type size_type;
typedef Vec::value_type value_type;
VecAdd(VecExpression<E1> const& u, VecExpression<E2> const& v) : _u(u), _v(v) {
assert(u.size() == v.size());
}
size_type size() const { return _v.size(); }
value_type operator[](Vec::size_type i) const { return _u[i] + _v[i]; }
};

这个类用于实现两个向量的加法。(一种具体的向量表达式类型)。我们可以清楚地看到,这个类就是上一篇文章里的延迟求值。

a+b被定义为保存了a和b的引用,构造了一个VecAdd类型的对象。


显然我们还需要定义一个operator+,以连接起VecAdd:

template <typename E1, typename E2>
VecAdd<E1,E2> const
operator+(VecExpression<E1> const& u, VecExpression<E2> const& v) 
{
return VecAdd<E1,E2>(u,v);
}

operator[] 实现了对两个向量对应元素相加。

我们看到,当遇到表达式:

Vec a,b;

Vec c=a+b;

首先,a和b均为Vec类型,调用上面的operator+函数(注意形参里使用的是基类类型,因此要用到类型转换!),构造一个VecAdd类型的对象。

当遇到Vec c=VecAdd(),这样一个表达式时,调用构造函数,对每一个成员进行复制,这时候又要调用VecAdd的元素存取运算符operator[],在此时完成了向量的加法。


为方便起见,我把一个完整的实现与测试程序贴在这儿供大家练习,加入其他的运算都是很直接了当的:

#include <vector>
#include <cassert>
#include "windows.h"
#include <iostream>
#pragma optimize("",on)
class hptime
{
LARGE_INTEGER sys_freq;
public:
hptime(){QueryPerformanceFrequency(&sys_freq);}   

double gettime()
{
LARGE_INTEGER tick;
QueryPerformanceCounter(&tick);
return (double)tick.QuadPart*1000.0/sys_freq.QuadPart;
}
};


//from: http://en.wikipedia.org/wiki/Expression_templates


template <typename E>
// A CRTP base class for Vecs with a size and indexing:
class VecExpression 
{
public:
typedef std::vector<double>         container_type;
typedef container_type::size_type   size_type;
typedef container_type::value_type  value_type;
typedef container_type::reference   reference;
//get the size
size_type size() const { return static_cast<E const&>(*this).size(); }
//access the element using []
value_type operator[](size_type i) const { return static_cast<E const&>(*this)[i]; }
//function call operator(), const variation for other type applications
operator E&() { return static_cast<E&>(*this); } //
operator E const&() const { return static_cast<const E&>(*this); }
};


// The actual Vec class:
class Vec : public VecExpression<Vec> 
{
container_type _data; //stl vector type
public:
//access vector elements
reference operator[](size_type i) { return _data[i]; }
value_type operator[](size_type i) const { return _data[i]; }
//access vector size
size_type size() const { return _data.size(); }
//constructor
Vec(size_type n) : _data(n) {} // Construct a given size:

// Construct from any VecExpression:
template <typename E>
Vec(VecExpression<E> const& vec) 
{
E const& v = vec;
_data.resize(v.size());//clear and resize, why we need this???it is already allocated
for (size_type i = 0; i != v.size(); ++i) 
{
_data[i] = v[i];
}
}


template <typename E>
//Vec(VecExpression<E> const& vec) 
Vec& operator=(VecExpression<E> const& vec)
{
E const& v = vec;
//_data.resize(v.size());//clear and resize, why we need this???it is already allocated
for (size_type i = 0; i != v.size(); ++i) 
{
_data[i] = v[i];
}
return (*this);
}
};


//vector addition 
template <typename E1, typename E2>
class VecAdd : public VecExpression<VecAdd<E1, E2> > 
{
E1 const& _u;
E2 const& _v;
public:
typedef Vec::size_type size_type;
typedef Vec::value_type value_type;
VecAdd(VecExpression<E1> const& u, VecExpression<E2> const& v) : _u(u), _v(v) {
assert(u.size() == v.size());
}
size_type size() const { return _v.size(); }
value_type operator[](Vec::size_type i) const { return _u[i] + _v[i]; }
};


// Now we can overload operators:
template <typename E1, typename E2>
VecAdd<E1,E2> const
operator+(VecExpression<E1> const& u, VecExpression<E2> const& v) 
{
return VecAdd<E1,E2>(u,v);
}




#include <iostream>
using namespace std;
int main()
{
hptime t0;
const int n=1000000;
int i,j,k;
double dtime;
double *v10,*v20,*v30;
Vec v1(n),v2(n),v3(n);


v10=&v1[0];
v20=&v2[0];
v30=&v3[0];
for(i=0;i<n;i++)
{
v1[i]=i;
v2[i]=i*1.1;
}

dtime=t0.gettime();
v3=v2+v1; //call operator=
dtime=t0.gettime()-dtime;
cout<<"Expression template: "<<dtime<<" ms\n";
//compare with direct double calculation
dtime=t0.gettime();
for(i=0;i<n;i++) 
v30[i]=v20[i]+v10[i];
dtime=t0.gettime()-dtime;
cout<<"Vector element: "<<dtime<<" ms\n";

}






原创粉丝点击