写在前面的话:模板充斥着无限的可能性
表达式模板和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;
}