表达式模板解决的问题是:
对于一个数值数组类,它需要为基于整个数组对象的数值操作提供支持,如对数组求和或放大:

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

对效率要求苛刻的数值计算器,会要求上面的表达式以最高效的方式进行求值。想既高效又以这种比较紧凑的运算符写法来实现,表达式模板可以帮助我们实现这种需求。

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

18.1 临时变量和分割循环
书中本节讲解了使用普通的运算符重载方法,临时变量的构造和多次的读写操作带来的低效,以及使用包含计算的赋值运算符(如+=,*=等),即便可以避免过多的临时变量构造,却依旧进行了多次读写以致并没有带来明显的效率提高,同时引入并不雅观的代码。
有兴趣详见书籍;

18.2 在模板实参中编码表达式
前面的问题中,我们注意到:直到看到了整个表达式的时候(在我们的例子中,即在调用赋值运算符的时候),才对表达式的各个部分进行求值。下面我们要做的,就是把表达式:

1.2*x + x*y;

转化为一个具有如下类型的对象:

A_Add< A_Mult<A_Scalar<double>, Array<double> >,
A_Mult<Array<double>, Array<double> > >

18.2.1 表达式模板的操作数
首先我们来实现A_Add和A_Mult,A_Scalar类:
---------------------- 标识1 ------------------------

// exprtmpl/exprops1.hpp

#include <stddef.h>
#include <cassert>
// 包含了一个辅助class trait template, 从而可以根据不同的情况,判断究竟是以“传值”的方式还是以“传引用”的方式来引用对应的“表达式模板节点”:
#include "exprops1a.hpp" // 表示两个操作数之和的对象的所属类
template <typename T, typename OP1, typename OP2>
class A_Add
{
private:
// 这里使用辅助类A_Traits的原因参见 “标识4”
typename A_Traits<OP1>::ExprRef op1; // 第1个操作数
typename A_Traits<OP2>::ExprRef op2; // 第2个操作数 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代表最大的容量(大小)
size_t size() const
{
assert (op1.size() == || op2.size() == || op1.size() == op2.size() );
return op1.size() != ? op1.size() : op2.size();
}
}; // 表示两个操作数之积的对象的所属类
template <typename T, typename OP1, typename OP2>
class A_Mult
{
private:
typename A_Traits<OP1>::ExprRef op1; // 第1个操作数
typename A_Traits<OP2>::ExprRef op2; // 第2个操作数 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代表最大的容量(大小)
size_t size() const
{
assert (op1.size() == || op2.size() == || op1.size() == op2.size() );
return op1.size() != ? op1.size() : op2.size();
}
}; exprtmpl/exprscalar.hpp
// 用于表示放到倍数的对象的所属类
template <typename T>
class A_Scalar
{
private:
T const& s; // scalar的值
public:
// 构造函数,用于初始值
A_Scalar (T const& v) : s(v) { } // 对于索引(下标)操作而言,每个元素的值都等于scalar(放大倍数)的值
T operator[] (size_t) const {
return s
} // scalar 的大小(即元素个数)为0
size_t size() const{
return ;
}
};

18.2.2 Array 类型
---------------------- 标识2 ------------------------
既然能够使用轻量级的表达式模板来对表达式进行编码,下面我们创建一个Array:它既能够针对占用实际内存的数组,同时也适用于表达式模板。

// Rep类型要么是SArray,但前提是Array必须是一个占用实际存储空间的数组;要么是一个用于编码表达式的嵌套template-id,如A_Add和A_Mult。
// exprtmpl/exprarray.hpp
#include <stddef.h>
#include <cassert>
#include "sarray1.hpp" template <typename T, typename Rep = SArray<T> >
class Array
{
private:
Rep expr_rep; // (访问)数组的数据 public:
// 创建具有初始化大小的数组
explicit Array (size_t s) : expr_rep(s) { } // 根据其他可能的表示来创建数组
Array (Rep const& rb) ; expr_rep(rb) { } // 针对相同类型的赋值运算符
Array& operator = (Array const& b) {
assert (size() == b.size() );
for (size_t idx = ; idx < b.size(); ++idx)
{
expr_rep[idx] = b[idx];
}
return *this;
} // 针对不同类型的赋值运算符
template <typename T2, typename Rep2>
Array& operator = (Array<T2, Rep2> const& b) {
assert (size() == b.size() );
for (size_t idx = ; idx < b.size(); ++idx)
{
expr_rep[idx] = b[idx];
}
return *this;
} // size 是所表示数据的大小
size_t size() const{
return expr_rep.size();
} // 分别针对常量和变量的索引(下标)运算符
T operator[] (size_t idx) const {
assert(idx < size() );
return expr_rep[idx];
} T& operator[] (size_t idx) const {
assert(idx < size() );
return expr_rep[idx];
} // 返回数组现在所表示的对象
Rep const& rep() const {
return expr_rep;
} Rep& rep(){
return expr_rep;
}
};

18.2.3 运算符
---------------------- 标识3 ------------------------
到目前为止,我们只是实现了用于代表运算符的、针对数值Array模板的运算符操作(诸如A_Add),但仍然没有实现运算符本身(诸如+)。下面我们来做这件事:

// exprtmpl/exprops2.hpp
// 两个数组相加
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() ));
} // 数组和scalar相乘
// scalar和数组相加
// 数组和scalar相加
...

18.2.4 总结
针对前面的例子代码,我们来进行一个自顶向下的回顾:

int main()
{
Array<double> x(), y();
...
x = 1.2*x + x*y;
}

(1)首先,编译器解析最左边的*运算符,它是一个scalar-array运算符:

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() )); // 构造函数参数
}

其中操作数的类型是double和Array<double, SArray<double> >。因此,实际的结果类型是:

Array<double, A_Mult<double, A_Scalar<double>, SArray<double> > >

而结果值是一个构造自double值1.2的A_Scalar<double>对象,和一个表示对象x的SArray<double>对象。

(2)接下来,将会对第2个乘法进行求值:x*y是一个array-array操作:

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() ));
}

而两个操作数的类型都是Array<double, SArray<double> >, 因此结果类型为:

Array<double, A_Mult<double, SArray<double>, SArray<double> > >

这一次,A_Mult所封装的两个参数对象都引用了一个SArray<double>表示:即一个用于表示x对象,另一个用于表示y对象。

(3)最后,才对+运算符进行求值。这次还是array-array操作:

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() ));
}

其中用double来替换T,R1则用:

A_Mult<double, A_Scalar<double>, SArray<double> >

进行替换,而R2则替换为:

A_Mult<double, SArray<double>, SArray<double> >

因此,赋值运算符右边的表达式最终的类型为:

Array<double,
A_Add<double,
A_Mult<double, A_Scalar<double>, SArray<double> >,
A_Mult<double, SArray<double>, SArray<double> > > >

这个类型将与Array模板的赋值运算符模板进行匹配:

template <typename T, typename Rep = SArray<T> >
class Array
{
public:
... // 针对相同类型的赋值运算符
Array& operator = (Array const& b) {
assert (size() == b.size() );
for (size_t idx = ; idx < b.size(); ++idx)
{
expr_rep[idx] = b[idx];
}
return *this;
} ...
};

其中赋值运算符将会运用右边Array(即b)的下标运算符来计算目标数组x的每一个元素,其中右边Array的实际类型为:

A_Add<double,
A_Mult<double, A_Scalar<double>, SArray<double> >,
A_Mult<double, SArray<double>, SArray<double> > >

如果我们仔细跟踪这个下标操作,那么对于一个给定的下标x,将会得到:

b[idx]实际展开成:(1.2*x[idx]) + (x[idx]*y[idx])

也即: x = 1.2*x + x*y;

表达式首先根据"标识3"规则进行展开,然后,再进一步根据"标识1"进行展开,在"标识1"中,针对每一个原始类型执行实际的数值操作(包括下标运算符操作)。

---------------------- 标识4 ------------------------
标识1中的运算符类使用了一个辅助类A_Traits,来定义操作数成员,是必要的:

 typename A_Traits<OP1>::ExprRef op1;        // 第1个操作数
typename A_Traits<OP2>::ExprRef op2; // 第2个操作数

原因在于:通常而言,我们可以把这些操作数声明为引用类型,因为大多数局部节点是在顶层表达式进行绑定的,因此它们的生命周期能够延续到完整表达式的求值。但是,唯一的例外是A_Scalar节点,它是在运算符函数内部进行绑定的,所以并不能一直存在到完整表达式的求值。因此,为了使放到倍数的成员能够一直存在到完整表达式求值,我们需求对scalar操作数进行“传值拷贝”,而不是“传引用拷贝”。也即:

(1)通常情况下是常数引用:

OP1 const& op1;     // 指向第1个操作数的引用
OP2 const& op2; // 指向第2个操作数的引用

(2)对于scalar值,则是普通值:

OP1 op1;        // 以传值拷贝的方式引用第1个操作数
OP2 op2; // 以传值拷贝的方式引用第2个操作数

故而,trait class 定义如下:它定义了一个针对大多数常数引用的基本模板,但同时定义了一个针对scalar的特化:

// exprtmpl/expropsla.hpp

/* 用于选择如何引用“表达式模板节点”的辅助trait class
* - 通常情况下:传引用
* - 对于scalar:传值
*/ template <typename T> class A_Scalar; // 基本模板
template <typename T>
class A_Traits
{
public:
typedef T cosnt& ExprRef; // 所引用的类型typedef成一个常量引用
}; // 针对scalar的局部特化
template <typename T>
class A_Traits<A_Scalar<T> >
{
public:
typedef A_Scalar<T> ExprRef; // 所引用的类型实际是一个普通值
};

C++ template —— 表达式模板(十)的更多相关文章

  1. Expression Template(表达式模板,ET)

    1.前言 在前一篇文章自己实现简单的string类中提到在实现+操作符重载函数时,为了防止返回时生成的临时对象调用拷贝构造函数动态申请内存空间,使用了一个叫move的函数,它是C++0x新增的特性.既 ...

  2. MShadow中的表达式模板

    表达式模板是Eigen.GSL和boost.uBLAS等高性能C++矩阵库的核心技术.本文基于MXNet给出的教程文档来阐述MXNet所依赖的高性能矩阵库MShadow背后的原理. 编写高效的机器学习 ...

  3. art template前端模板引擎

    偶然看到后台有一段代码 采用的是art template的模板引擎 地址为 http://aui.github.io/artTemplate/ 这段代码很简洁 var html = template( ...

  4. Template Method 模板设计模式

    什么是模板设计模式 对于不了解的模板设计模式的来说,可以认为如同古代的造纸术一样,纸所以成型,取决于用了模板的形状,形状又由镂空的木板组成,而你想要造什么纸,又取决于你使用什么材料. 上面提到了两个关 ...

  5. Atitit.eclipse comment  template注释模板

    Atitit.eclipse comment  template注释模板 1. Code templet1 1.1. Settpath1 1.2. 设置存储1 1.3. 导出设置1 2. Java d ...

  6. vue-learning:12-1- HTML5的<template>内容模板元素

    HTML5的<template>内容模板元素 HTML内容模板<template>元素将它其中的内容存储在页面文档中,以供后续使用,该内容的DOM结构在加载页面时会被解析器处理 ...

  7. Vue2.0 【第二季】第5节 Template制作模板

    目录 Vue2.0 [第二季]第5节 Template制作模板 第5节 Template制作模板 一.直接写在选项里的模板 二.写在template标签里的模板 三.写在script标签里的模板 Vu ...

  8. C++ template —— 深入模板基础(二)

    上一篇C++ template —— 模板基础(一)讲解了有关C++模板的大多数概念,日常C++程序设计中所遇到的很多问题,都可以从这部分教程得到解答.本篇中我们深入语言特性.------------ ...

  9. Git Commit Template 提交模板

    多人协作开发一个项目时,版本控制工具是少不了的,git是linux 内核开发时引入的一个优秀代码管理工具,利用它能很好使团队协作完成一个项目.为了规范团队的代码提交,也方便出版本时的release n ...

随机推荐

  1. Airtest 网易 UI 自动化工具 Airtest 浅用记录

    一 使用目的 该工具主要是面向游戏UI测试基于图像识别,如游戏框架unity,Cocos-js以及网易内部的游戏框架同时也支持原生Android App 的基于元素识别的UI自动化测试.本文主要使用目 ...

  2. Axiom3D:Ogre中Mesh文件格式分析(一)

    在Axiom3D,或者说是Ogre的mesh的文件格式我们可能通过代码反推出相关格式,相关过程本来我是直接写的,后面发现相关流程写完后,我自己都看晕了,然后我就把一些过程用Execl整理出来,发现过程 ...

  3. hibernate+pageBean实现分页dao层功能代码

    今天闲来无事,摆弄了一下分页,突然发现很多代码长时间不用就生梳了,虽然有些基础,但没有一篇整合的.这里还是简单示例,主要是以后自己翻着看不用百度找啊找找不到一篇想要的 1.PageBean实体类,一页 ...

  4. QWSLock::up(): Invalid argument

    运行qt时,点击QMessageBox的确定按钮是出现错误QWSLock::up(): Invalid argument, QWSLock::down(): Invalid argument,这个是q ...

  5. 在js中 把 json对象转化为String对象的方法

    方法1: 使用js的扩展方法 ** * json对象转字符串形式 */ function json2str(o) { var arr = []; var fmt = function(s) { if ...

  6. ie10以上媒体查询 css

    @media screen and (-ms-high-contrast: active), (-ms-high-contrast: none) { }

  7. int[,] 和 int[][] 有什么区别

    int[,] 是二维数组,它就是传统意义上 n x m 的表,和 C++ 里的 int[][] 是一个意思. int[][] 是交错数组,与 C++ 里的 int[][] 不同.它其实是一个 int[ ...

  8. Hibernate与MyBatis的对比

    Hibernate与MyBatis的对比总结,希望大家指出不对之处. 第一章 Hibernate与MyBatis Hibernate 是当前最流行的O/R mapping框架,它出身于sf.net,现 ...

  9. PHP安全之临时文件的安全

    (一)临时文件简介临时文件,顾名思义是临时的文件,文件的生命周期短.然而,很多应用的运行都离不开临时文件,临时文件在我们电脑上无处不在,通常有以下几种形式的临时文件: 文件或图形编辑程序,所生成的中间 ...

  10. Effective STL读书笔记

    Effective STL 读书笔记 本篇文字用于总结在阅读<Effective STL>时的笔记心得,只记录书上描写的,但自己尚未熟练掌握的知识点,不记录通用.常识类的知识点. STL按 ...