一种高效的 vector 四则运算处理方法
实现 vector 的四则运算
这里假设 vector 的运算定义为对操作数 vector 中相同位置的元素进行运算,最后得到一个新的 vector。具体来说就是,假如 vector<int> d1{1, 2, 3}, d2{4, 5, 6};则, v1 + v2 等于 {5, 7, 9}。实现这样的运算看起来并不是很难,一个非常直观的做法如下所示:
vector<int> operator+(const vector<int>& v1, const vector<int>& v2) {
// 假设 v1.size() == v2.size()
vector<int> r;
r.reserve(v1.size());
for (auto i = 0; i < v1.size(); ++i) {
r.push_back(v1[i] + v2[i]);
}
return r;
}
// 同理,需要重载其它运算符
我们针对 vector 重载了每种运算符,这样一来,vector 的运算就与一般简单类型无异,实现也很直白明了,但显然这个直白的做法有一个严重的问题:效率不高。效率不高的原因在于整个运算过程中,每一步的运算都产生了中间结果,而中间结果是个 vector,因此每次都要分配内存,如果参与运算的 vector 比较大,然后运算又比较长的话,效率会比较低,有没有更好的做法?
既然每次运算产生中间结果会导致效率问题,那能不能优化掉中间结果?回过头来看,这种 vector 的加减乘除与普通四则运算并无太大差异,在编译原理中,对这类表达式进行求值通常可以通过先把表达式转为一棵树,然后通过遍历这棵树来得到最后的结果,结果的计算是一次性完成的,并不需要保存中间状态,比如对于表达式:v1 + v2 * v3,我们通常可以先将其转化为如下样子的树:

因此求值就变成一次简单的中序遍历,那么我们的 vector 运算是否也可以这样做呢?
表达式模板
要把中间结果去掉,关键是要推迟对表达式的求值,但 c++ 语言成面上不支持 lazy evaluation,因此需要想办法把表达式的这些中间步骤以及状态,用一个轻量的对象保存起来,具体来说,就是需要能够将表达式的中间步骤的操作数以及操作类型封装起来,以便在需要时能动态的执行这些运算得到结果,为此需要定义类似如下这样一个类:
enum OpType {
OT_ADD,
OT_SUB,
OT_MUL,
OT_DIV,
};
class VecTmp {
int type_;
const vector<int>& op1_;
const vector<int>& op2_;
public:
VecTmp(int type, const vector<int>& op1, const vector<int>& op2)
: type_(type), op1_(op1), op2_(op2) {}
int operator[](const int i) const {
switch(type_) {
case OT_ADD: return op1_[i] + op2_[i];
case OT_SUB: return op1_[i] - op2_[i];
case OT_MUL: return op1_[i] * op2_[i];
case OT_DIV: return op1_[i] / op2_[i];
default: throw "bad type";
}
}
};
有了这个类,我们就可以把一个简单的运算表达式的结果封装到一个对象里面去了,当然,我们得先将加法操作符(以及其它操作符)重载一下:
VecTmp operator+(const vector<int>& op1, const vector<int>& op2) {
return VecTmp(OT_ADD, op1, op2);
}
这样一来,对于 v1 + v2,我们就得到了一个非常轻量的 VecTmp 对象,而该对象可以很轻松地转化为 v1 + v2 的结果(遍历一遍 VecTmp 中保存的操作数)。但上面的做法还不能处理 v1 + v2 * v3 这样的套嵌的复杂表达式:v2 * v3 得到一个 VecTmp,那 v1 + VecTmp 怎么搞呢?
同理,我们还是得把 v1 + VecTmp 放到一个轻量的对象里,因此最好我们的 VecTmp 中保存的操作数也能是 VecTmp 类型的,有点递归的味道。。。用模板就可以了,于是得到如下代码:
#include <vector>
#include <iostream>
using namespace std;
enum OpType {
OT_ADD,
OT_SUB,
OT_MUL,
OT_DIV,
};
template<class T1, class T2>
class VecSum {
OpType type_;
const T1& op1_;
const T2& op2_;
public:
VecSum(int type, const T1& op1, const T2& op2): type_(type), op1_(op1), op2_(op2) {}
int operator[](const int i) const {
switch(type_) {
case OT_ADD: return op1_[i] + op2_[i];
case OT_SUB: return op1_[i] - op2_[i];
case OT_MUL: return op1_[i] * op2_[i];
case OT_DIV: return op1_[i] / op2_[i];
default: throw "bad type";
}
}
};
template<class T1, class T2>
VecSum<T1, T2> operator+(const T1& t1, const T2& t2) {
return VecSum<T1, T2>(OT_ADD, t1, t2);
}
template<class T1, class T2>
VecSum<T1, T2> operator*(const T1& t1, const T2& t2) {
return VecSum<T1, T2>(OT_MUL, t1, t2);
}
int main() {
std::vector<int> v1{1, 2, 3}, v2{4, 5, 6}, v3{7, 8, 9};
auto r = v1 + v2 * v3;
for (auto i = 0; i < r.size(); ++i) {
std::cout << r[i] << " ";
}
}
上面的代码漂亮地解决了前面提到的效率问题,扩展性也很好(能够方便地增加对其它运算类型的支持),而且对 vector 来说还是非侵入性的,当然了,实现上乍看起来可能就不是很直观了,除此也还有些小问题可以更完善:
- 操作符重载那里很可能会影响别的类型,因此最好限制一下,只针对
vector<int>和VecTmp<>进行重载,这里可以用 SFINAE 来处理。 VecTmp<>的 operator[] 函数中的 switch 可以优化掉,VecTmp<>模板只需增加一个参数,然后对各种运算类型进行偏特化就可以了。VecTmp<>对操作数的类型是有隐性要求的,只能是vector<int>或者是VecTmp<>,这里也应该用 SFINAE 强化一下限制,使得用错时出错信息可以好看些。
现在我们来重头再看看这一小段奇怪的代码,显然关键在于 VecTmp<> 这个模板,我们可以发现,它的接口其实很简单直白,但它的类型却可以是那么地复杂,比如说对于 v1 + v2 * v3 这个表达式,它的结果的类型是这样的: VecTmp<vector<int>, VecTmp<vector<int>, vector<int>>>,可以想象如果表达式再复杂些,它的类型也会跟着更复杂,如果你看仔细点,是不是还发现这东西和哪里很像?像一棵树,一棵类型的树:

这棵树看起来是不是还很眼熟,每个叶子结点都是 vector<int>,而每个内部结点则是由 VecTmp<> 实例化的:这是一棵类型的树,在编译时就确定了。这种通过表达式在编译时得到的复杂类型有一个学名叫: Expression template。在 c++ 中每一个表达式必产生一个结果,而结果必然有类型,类型是编译时的东西,结果却是运行时的。像这种运算表达式,它的最终类型是由表达式中每一步运算所产生的结果所对应的类型组合起来所决定的,类型确定的过程其实和表达式的识别是一致的。
而 VecTmp 对象在逻辑上其实也是一棵树,它的成员变量 op1_, op2_ 分别是左右儿子结点,树的内部结点代表一个运算,一个动作,左右儿子为其操作数,叶子结点则代表直接数(或 terminal),一遍中序遍历下来,得到的就是整个表达式的值。
神奇的 boost::proto
expression template 是个好东西(就正如 expression SFINAE 一样),它能帮助你根据给定的表达式,在编译时建立非常复杂好玩的类型(从而实现很多高级玩意,主要是函数式,EDSL 等)。但显然如果什么东西都需要自己从头开始写,这个技术用起来还是很麻烦痛苦的,好在模板元编程实在是个太好玩的东西,已经有很多人做了很多先驱性的工作,看看 boost proto 吧,在 c++ 的世界里再打开一扇通往奇怪世界的大门。
一种高效的 vector 四则运算处理方法的更多相关文章
- Java 开发者不容错过的 12 种高效工具
Java 开发者常常都会想办法如何更快地编写 Java 代码,让编程变得更加轻松.目前,市面上涌现出越来越多的高效编程工具.所以,以下总结了一系列工具列表,其中包含了大多数开发人员已经使用.正在使用或 ...
- Java集合系列(二):ArrayList、LinkedList、Vector的使用方法及区别
本篇博客主要讲解List接口的三个实现类ArrayList.LinkedList.Vector的使用方法以及三者之间的区别. 1. ArrayList使用 ArrayList是List接口最常用的实现 ...
- Linux下一种高效多定时器实现
Linux下一种高效多定时器实现 作者:LouisozZ 日期:2018.08.29 运行环境说明 由于在 Linux 系统下一个进程只能设置一个时钟定时器,所以当应用需要有多个定时器来共同管理程序运 ...
- 两种高效的事件处理模式(Proactor和Reactor)
典型的多线程服务器的线程模型 1. 每个请求创建一个线程,使用阻塞式 I/O 操作 这是最简单的线程模型,1个线程处理1个连接的全部生命周期.该模型的优点在于:这个模型足够简单,它可以实现复杂的业务场 ...
- C++的vector的使用方法
vector c++的vector的使用方法,创建,初始化,插入,删除等. #include "ex_vector.h" #include <iostream> #in ...
- linux几种快速清空文件内容的方法
linux几种快速清空文件内容的方法 几种快速清空文件内容的方法: $ : > filename #其中的 : 是一个占位符, 不产生任何输出. $ > filename $ echo & ...
- 【Android】一种提高Android应用进程存活率新方法
[Android]一种提高Android应用进程存活率新方法 SkySeraph Jun. 19st 2016 Email:skyseraph00@163.com 更多精彩请直接访问SkySeraph ...
- [转]SQL三种获取自增长的ID方法
最新更新请访问: http://denghejun.github.io SQL SERVER中的三种获得自增长ID的方法 这个功能比较常用,所以记下来以防自己忘掉. SCOPE_IDENTIT ...
- CSharpGL(40)一种极其简单的半透明渲染方法
CSharpGL(40)一种极其简单的半透明渲染方法 开始 这里介绍一个实现半透明渲染效果的方法.此方法极其简单,不拖累渲染速度,但是不能适用所有的情况. 如下图所示,可以让包围盒显示为半透明效果. ...
随机推荐
- 【原创】jmeter3.0在beanshell中输入中文乱码以及字体大小的更改
我使用的是最新的jmeter3.0版本,新建一个beanshell sampler,在里面输入中文,发现显示的是乱码,而且字体非常小,看着吃力,调研了一下,可以在bin/jmeter.properti ...
- atitit.404错误的排查流程总结vOa6
atitit.404错误的排查流程总结vOa6 1. 场景 1 1.1. 子应用猛个腊擦不能使用 404 兰.. 1 2. 服务器配置问题 2 2.1. 登录服务器管理子应用,查看应用是否启动okk ...
- Atitit. BigConfirmTips 控件 大数据量提示确认控件的原理and总结O9
Atitit. BigConfirmTips 控件 大数据量提示确认控件的原理and总结O9 1. 主要的涉及的技术 1 2. 主要的流程 1 3. 调用法new confirmO9t(); 1 4. ...
- jquery时间倒计时
代码: js: function countDown(time, id) { //time的格式yyyy/MM/dd hh:mm:ss var day_elem = $(id).find('. ...
- Liferay7 BPM门户开发之35: AssetTag的集成查询
Tag是liferay中的Asset特性,可以用来对信息进行分类,在iferay中的Asset类型为: 1. Web Content(自定义内容) 2. Documents and Media(文档库 ...
- ADO.NET笔记20160322
####ADO.NET ####1 启用sa验证与窗体相关知识 - 启用sa验证 - ShowDialog() ---- ####2 连接字符串 Data Source=服务器 ...
- 【CUDA学习】GPU硬件结构
GPU的硬件结构,也不是具体的硬件结构,就是与CUDA相关的几个概念:thread,block,grid,warp,sp,sm. sp: 最基本的处理单元,streaming processor 最 ...
- ios相关手册、图表等综合
Objective-C初学者速查表(来源:http://www.cocoachina.com/applenews/devnews/2013/1115/7362.html) iOS UIKit类图 (来 ...
- jquery实现返回基部案例效果
<!doctype html> <html> <head> <meta charset="gb2312"> <title> ...
- 转:ecshop商品分类页获取相册列表方法
ecshop商品分类页获取相册列表方法 很久之前就看到过你好在商品列表页有获取到相册列表,但是一直没有实践过,感觉应该挺简单的吧,但是最近手上的项目刚好就需要这个功能,然后就想到网上查下资料,至少找个 ...