为什么需要这么折腾?

我在主系统转为Linux后,虽然轻量级的Office编辑和图片/视频编辑是可以进行的,比如利用免费的LibreOffice、Kdenlive、Inkscape。对于图片处理,某些命令行工具如convert,可以直接脚本调用,灵活行和自动性反而比GUI软件更高。

但不得不承认,偶尔正式场合编辑文档表格和幻灯、PS、网银操作,还是逃脱不了更加普适的Windows。对于标准的Office套件,Adobe套件和Visual Studio等神一般的Windows软件,还是需要找一个解决方案来愉快地使用的。然而并没有入Mac的打算,所以我还是准备采用各种方法使用Windows。正好这样的话,也有机会再摸一摸Windows上的软件,比如微信电脑版等等。

所以对于我80%以上是直接使用Linux,一个纯粹的系统即可,有时需要使用Office等时需要短时间密集的Windows使用。

首先有两个方案也不错,但我不是很想使用,一个是Wine,兼容性总是永远的痛,一个是双系统,需要比较大的硬盘,而且相对比较麻烦,再后期与Windows to go对比后,我也抛弃了这个方案。

方案一: 虚拟机

虚拟机对于高性能的主机适用,对于我2012年的笔记本,运行XP非常流畅,但Win7比较吃力。虚拟机的优点很明显,做好系统后,几个文件组成的虚拟机可以任意备份与拷贝,所以我常备着几个干净的Xp和Win7虚拟机,和装有Office的。

vm

方案二: 远程桌面

在实验室有了高性能主机入驻后(研究生的开发机,非常眼馋),我便一直寻求办法远程使用它。在争取得到学长同意后,我登记了账号,并网上查询到方法,可以让多人同时登入一台Windows主机操作,这样当学长使用电脑的时候,我还是可以远程登陆进行操作。

关于操作顺畅的问题,Microsoft的RDP远程桌面协议已经十分高效,再加上如果是校园以太网连接,操作顺滑,甚至可以看视频。

使用远程桌面的好处也有不少:

  1. 不占用我本地的计算/存储资源,我可以同时开发与处理文档。
  2. 系统和我本机无关,我可以是Windows/Linux/Mac/iOS/Android,登陆即可享用主机资源。
  3. 只要网络通畅,随时随地登录。

当然也有一些弊端,比如受他人限制,远程桌面使用的虚拟显卡驱动会造成某些软件问题,需要插U盘的话比较麻烦(可以直接挂载本地硬盘共享)。

推荐一个Linux上的开源远程桌面客户端,FreeRdp,可以添加剪贴板共享、挂载本地磁盘等功能,其他平台微软均有提供。
vm

方案三: 外置SSD安装Windows to go

不过虚拟机在我笔记本上总是跑不畅快,远程桌面也并不是随时都能享用,怎样才能一直运行我的Windows(各种环境齐全),但又不受机器的限制,也就是不写死在一台主机中呢?

事实证明Windows to go就是符合我的期望的,它可以直接将Windows装在外置硬盘和U盘中。这样我只需要将这个外置存储设备插入一台主机,便选择从外置存储直接启动了,这里具体请参考微软的说明,其会在启动时判别硬件并加载驱动,就算不是同一机器,也能进入同一个Windows实例。Chrome OS则更方便,登录即可,不过现在还不适用于我的情况,技术上也不成熟。

USB3.0可以达到5Gb/s的传输速率,高于Sata3的极限,所以普通Sata3硬盘是可以这样用的,也可以弄一个高速USB3.0 U盘,但为了以后硬盘可以重用于其他,我还是牺牲了便携性。

Windows 8 + HDD + USB 3.0

于是我在十一期间立马找了一个320G的硬盘,配合一个USB3的外置盒,边体验了一番。
事实证明体验很差,不是因为系统不好用,而是因为HDD加劣质的外置盒太慢了。

两秒的延时,太慢了。
lantency

在看到其他人用外置SSD做iMac系统盘后,我才终于决定一定需要弄一个入门的SSD来做Windows to go,这样IO便不是瓶颈,如果有高性能的主机的话,我便可以直接占领其计算资源,作图渲染视频就能节省很多时间。

Windows 10 + SSD + USB3.0(UASP支持)

购买

硬盘性能、USB3.0协议、外置盒的主控芯片的性能,支持UASP协议都是值得考虑的,综合经济实力和使用频率考虑,由于只要装系统/Office/Adobe,120G足够,如果需要编辑的文件很大(如视频),可以后再加移动固态硬盘。

USAP是USB-IF所制定的一个新传输协议用于透过USB接口连接序列设备,提供大容量储存设备的传输速度高达20%,并降低CPU的利用率、数据延迟和等待时间,USAP协议提供了高性能主机及设备之间的数据传输。

最终选择:
SSD : 入门级120G Sandisk加强版,京东购入比较放心。
外置盒: ORICO 2589S3,USB3.0并支持UASP,淘宝购入。

共356元。

安装

  1. 安装SSD,盒子是免工具拆卸,立马搞定。
  2. 格式化为NTFS,测速发现不错。
  3. 使用rufus工具,选择Windows 10镜像,并勾选Windows to go,过程非常傻瓜。
  4. 重启选择外置SSD启动。

rufus

使用

装完各种常用应用后硬盘使用不超一半,测速满意,和同学的低端SSD系统盘对比后发现差距不大。

rufus
rufus

相比之下原来硬盘的顺序读写不到50MB/s,4K根本跑不完。

正好开发机性能不错,只安装了Ubuntu,也启动尝试了一下。

rufus
rufus

最终认为这次折腾是值得的,终于可以再次畅快地玩玩Windows 10了,并且所有环境都在一个随时硬盘中,节省了重复布置环境的麻烦。

问题描述

目前在做的小项目中,我们在浏览器中通过Hook浏览器的API,截获目标程序对浏览器API的调用。比如对于Webgl的网页程序,我们就能截获所有的gl指令,这其中主要是截获参数列表。把这些gl指令翻译为Opengl ES标准的话便可以编译为一个其他设备上的原生应用了。

图形的绘制过程中经常会有大量的数组需要传递入GPU,这就造成了我们截获了大量的数组。如果想直接把这些数据保存在浏览器内存中,压力很大,实时流到其他服务器上的话也带来了stringfy瓶颈和传输瓶颈。虽然对于Javascript中的Typed Array,我们现在直接二进制传输到服务器上,是否能只传输变化的数组,或者是只传输数组的delta,这个是一个潜在的优化。当然,如果发现大部分要传输的数组都是改变过的话,或者是检测改变的开销大于直接传输的开销(毕竟是CPU时间对抗IO时间,而且由于检测比如会使目标程序运行变慢),完全便可以不考虑进行数组改变的检测了。

对于给定Javascript的TypedArray,我们的目标便是当截获到的gl指令需要使用它的时候,我们需要能知道它是否内容发生了改动,更进一步能记录出上次截获到这次截获间的delta。
花了一些时间进行探索,然而最后还是无解,并也没有方法能占用资源尽量少地做到标记修改,记录如下。

暴力检测

这个方法不需要多说,不过检测的时间消耗太多,由于消耗了Webgl每帧的CPU时间,很容易造成CPU性能瓶颈,导致Fps下降。

Object.observe() / Array.observe()

最先查询到的便是两个内建的API,直接来看MDN上的使用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
var obj = {
foo: 0,
bar: 1
};

Object.observe(obj, function(changes) {
console.log(changes);
});

obj.baz = 2;
// [{name: 'baz', object: <obj>, type: 'add'}]

obj.foo = 'hello';
// [{name: 'foo', object: <obj>, type: 'update', oldValue: 0}]

delete obj.baz;
// [{name: 'baz', object: <obj>, type: 'delete', oldValue: 2}]
```

一开始看到的时候感觉非常符合我的需求,并且还区分了改动的类型,比如updateadddelete类型。
应该现在很多前端的工具就使用Observe便可以完全数据到显示的单向同步了。
于是尝试了一下在Typed Array上的操作:

1
2
3
4
5
6
7
8
9
10
var obj = new Int16Array([1,2,3]);
Object.observe(obj, function(changes) {
console.log(changes);
});

obj[0] = 9; // changed
obj[0] = 9; // unchanged
obj.sort(); // changed

```

不过后来看到这么一个解释:

The Object.observe() method is used for asynchronously observing the changes
to an object. It provides a stream of changes in the order in which they
occur.

说明变更事件是异步通知的,比如可以这样实验一下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var a = new Int16Array([1,2,3]);
a.isDirty = false;
Object.observe(a, function(changes) {
a.isDirty = true;
console.log('c');
});
a[0]=9;
console.log(a.isDirty);
setTimeout('console.log(a.isDirty);',100);

output:
false
c
true
```

这就带来了问题,由于Javascript执行是单线程的,可能在webgl程序刚修改完后便被我截获了这个数组,但修改通知没到,我就已经错误判断数组未改变了,由于我相当于是对每个gl命令的参数进行快照,无法在未来进行记录,如果停止我当前线程的,会带来严重的性能问题。

Object.defineProperty

这个方法可以为对象定义属性,并能对属性的访问进行详细的控制,最简单的话我们给定义get和set函数,举例说明:

1
2
3
4
5
6
7
8
9
10
11
12
13
var arr = {};
var shadow = [1,2,3];
Object.defineProperty(arr, '0', {
get: function() {
console.log('get');
return shadow['0'];
},
set: function(v) {
console.log('set');
shadow['0'] = v;
}
});
```

通常对外暴露的get,set中要访问一个内部的变量,以防止递归访问属性的get或是set。
这个方法其实是多加入了一个proxy的对象。并且TypedArray并不能重写数字属性的get/set,只能通过一个proxy对象了。

一个简陋的实现,直接替换浏览器中提供的API:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
var _Float32Array = Float32Array;
Float32Array = function () {
// 这里需要罗列出内部的属性和函数名
// 因为数组类型只能for遍历数字属性
var list = [
"BYTES_PER_ELEMENT",
"__defineGetter__",
"__defineSetter__",
"__lookupGetter__",
"__lookupSetter__",
"constructor",
"buffer",
"byteLength",
"byteOffset",
"entries",
"hasOwnProperty",
"isPrototypeOf",
"keys",
"length",
"propertyIsEnumerable",
"set",
"subarray",
"toLocaleString",
"toString",
"valueOf",
"values",
]

// 初始化的函数可能会有三个参数,还未处理
var a = new _Float32Array(arguments[0]);

this.a = a;
var b = this;
this.isDirty = false;

list.forEach(function (key) {
if (typeof(a[key]) == 'function') {
b[key] = function () {
b.isDirty = true;
console.log('invoke', key);
return a[key].apply(a, arguments);
}
}else {
Object.defineProperty(b, key, {
configurable: true,
enumerable: true,
get: function(i) {
return function () {
console.log('get member',i);
return a[i];
}
}(key),
set: function(i) {
return function (v) {
b.isDirty = true;
console.log('set member',i);
a[i] = v;
}
}(key)
});
}
})

this.constructor = a.constructor;

for (var idx in a) {
Object.defineProperty(b, idx, {
configurable: true,
enumerable: true,
get: function(i) {
return function () {
console.log('get index',i);
return a[i];
}
}(idx),
set: function(i) {
return function (v) {
console.log('set index',i);
b.isDirty = true;
a[i] = v;
}
}(idx)
});
}
}

// 测试
>>a = new Float32Array([1,2,3])
<<Float32Array {a: Float32Array[3], isDirty: false}
>>a.toString()
<<t.js:64 invoke toString
<<"1,2,3"
>>a[0]=1
<<t.js:100 set index 0
<<1
>>a[2]
<<t.js:94 get index 2
<<3
>>a.constructor
<<Float32Array() { [native code] }
```

本做法应该还有以下局限:

性能

大规模下创建一个1000000的数组内存占用飙升,因为创建了太多的get/set函数。

1
2
3
4
5
6
7
8
9
10
11
ori = new _Float32Array(1000000)
Float32Array[1000000]
mod = new Float32Array(1000000)
Float32Array {a: Float32Array[1000000], isDirty: false}

console.time('a');for(i=0;i<1000000;i++){var t = mod[i];t++;mod[i]=t;};console.timeEnd('a');
VM741:2 a: 5313.409ms

console.time('a');for(i=0;i<1000000;i++){var t=ori[i];t++;ori[i]=t;};console.timeEnd('a');
VM743:2 a: 3884.074ms
```

内部创建并返回的TypedArray无法Hook

比如类似subarray()函数,返回值便是原生的TypedArray了,需要在Proxy对象中特殊处理一下。

在某些场景下不能完全替代原有的TypedArray

可以尽量模拟原生TypedArray的对外Api,不过还是有些场景可能照顾不到。

ES6 Proxy & Reflect

Chrome中还没有实现,使用Firefox一试。对某对象加一层通用的代理,非常容易实现。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

var ori = new Float32Array(1000000);
var obj = new Proxy(aa, {
get: function (target, key, receiver) {
return Reflect.get(target, key, receiver);
},
set: function (target, key, value, receiver) {
return Reflect.set(target, key, value, receiver);
}
});


console.time('a');for(i=0;i<1000000;i++){var t = aa[i];t++;aa[i]=t;};console.timeEnd('a');
a: 计时器开始
a: 1738.62ms
console.time('a');for(i=0;i<1000000;i++){var t = obj[i];t++;obj[i]=t;};console.timeEnd('a');
a: 计时器开始
a: 3226.16ms

```

如果还有更好的方法,欢迎讨论。

为什么需要使用 Web Page Replay?

需求的产生

实习期间,我们经常用各种Workload对浏览器进行测试,初期大量的Workload都是手工收集或是开源的,再整理到小组内的服务器上。
后期产生了抓取浏览器中Javascript Trace并实时送至后端分析的需求,例如我们希望将某些网页的Webgl调用全部记录下来,或者是将用户的各种互动事件记录下来,又不用手工地修改Workload,便有了我目前写的代理服务器,它能对我们关注的Workload注入Javascript脚本,比和后端代理服务器建立通信,高效地把Trace发送到后端,并易于扩展各种功能的后端。

一个更加常见的需求便是Localize Real life Workload,我们需要自动化地测试很多网上不开源的Workload,虽然通过我的代理服务器,注入分析不需要人工干预了,不过在公司内连接外网Workload是痛苦的。尤其是某些Workload每次需要加载50M的资源文件,一旦浏览器cache失效便要登上很长时间。而且网上的Workload在不断变动,无法直接进行组内Workload的统一化,针对浏览器测试时很难做到组内环境一致。

所以一个最基本的需求就是我们访问该Workload一次,就将所有经过代理服务器的request和response本地存档,称为record;等到下次重复测试时,匹配所有的request,直接发回对应的response,这样便能做到本地replay该workload了。

最近进哥说想加入这个功能在我们的代理服务器上,但由于当初没有考虑到这个功能,并且组内提出可以复用Telemetry中的Webpage Replay功能,可以将其加在我代理服务器的外层,便可以像是管道一样进行功能的扩展。正好我最近在看的Webgl Workload需要加载的资源很多,进行Replay极大加快了我自测的速度。

HTTP/HTTPS代理服务器的妙用

刚才说的两个需求只是代理服务器的一个很小的用处,还有大量的妙用如下:

  • 科学上网,不过HTTP代理一般只能作为国内跳板。
  • 反向代理,做网站的入口,自动后端负载均衡、流量审查、压缩、简单的逻辑直接部署等等功能。
  • 测试平台,之前看到阿里的Anyproxy便是这个作用,可以记录下所有接入终端(手机、平板)的HTTP网路请求,方便修改Header,Cookie,进行颠簸的网路环境的模拟等等,也提供Replay。
  • 内网上网,公司内部方便屏蔽外界的攻击,并限制员工上网,审查上网记录,当然有福利便是可以直接提供梯子。

Web Page Replay 实现原理的简单理解

Replay工具可以运行在DNS劫持和HTTP代理两种模式下,实质上DNS劫持简化了设置浏览器代理的过程。

replay

盗图一张,可以看出Replay劫持模式便是在本机启动一个DNS服务器和Web服务器,分别占用53和80端口。
一般情况下,浏览器访问一个页面先进行域名的DNS解析,然后先目标服务器发送request。Record时,DNS全部返回127.0.0.1,浏览器便将请求发送给本机的服务器,服务器随后便代理请求后返回,并记录下本次request和response。Replay时本机服务器直接匹配存档文件。
DNS劫持的好处便是不用设置浏览器代理了,缺点也很明显:

  • 如果request写明ip时,便绕过了本地的DNS请求(类似Host梯子),便无法实现replay。
  • 访问端口不是标准的80和443也会直接在record环节出错。
  • 需要占用本机的53,80,443端口,影响本机的原有的服务。

所以这个工具也支持直接设置代理的模式,如果只是浏览器的话使用比较简单。想将多个代理服务器管道化,设置每一层的代理为上一层代理就可以了。

接下来粗略了解一下代理服务器的工作方式。

对于HTTP:

代理服务器通过TCP Socket接收到来自客户端的请求。它从HTTP的Header中解析出host和port,如果是域名的话先DNS解析,然后就是Roundtrip最终返回真实的response,这里所有的内容都是代理服务器可见的,可以随意修改。

如果我们这里使用nc,可以首先不通过代理访问百度:

1
2
3
$ nc baidu.com 80
GET / HTTP/1.1
Host: baidu.com

可以看见返回的response:

1
2
3
HTTP/1.1 200 OK
Date: Fri, 14 Aug 2015 07:55:26 GMT
......

如果通过本机8080代理服务器:

1
2
3
$ nc localhost 8080
GET http://baidu.com HTTP/1.1
Host: baidu.com

代理服务器可能会输出如下日志:

1
2
3
INFO: Got request  baidu.com GET http://baidu.com
INFO: Sending request GET http://baidu.com
INFO: Received response 200 OK

对于HTTPS:
代理服务收到客户端的CONNECT请求,但只可见host和port,代理服务器只能在客户端和服务器中建立起socket连接并相互双工传递,如果需要修改内容的话,需要进行中间人攻击。

Web Page Replay 使用实战

使用DNS劫持模式

使用直接代理模式

源码分析

我学的第一门语言便是C++,不过由于后期一直没有使用C++写过较大的项目,一直处于入门阶段。最近实习,第一个小项目便是搭建一个代理服务器,Manager处于对性能的要求,希望我用C++实现。当时比较懒,觉得用C++重写代理服务器进展会比较慢,便说服Manager让我使用Golang,配合一些现有的框架实现。现在看起来达到了预期效果,但也缺少了锻炼C++能力的机会。

现有的项目中,Android App几乎都是使用NDK进行开发的,近期我们有进行重构和性能优化的打算。恰逢其机,进哥在每周的Code Study上进行了 C++11 新标准的分享,我也借助周末的机会稍作整理。

概览

timeline

C++自98标准化后,变动不大。直到近年开始不断推出新的标准,我们现在似乎还处在不断改进的中期。

按照Manager的说法,改动可以被划分入如下几类:

  • 语法糖类。让代码更加简洁,易于理解。
  • 显式地进行语义申明。填之前留下的坑,并让代码更加符合最佳实践。
  • 其他语言的新特性。
  • 标准库的完善。

Manager之前是做编译器的,所以他觉得大部分修改是标准委员会拍拍大脑制定的。的确,语言的设计不是大杂烩,维持一个简洁规范的语法规则是很有必要的,C++由于需要向前兼容,看起来便不简洁了。

语法糖

这部分是Manager痛斥的,不过也是我们大家喜闻乐见的一些小改进。

Auto关键字

1
2
3
4
auto x = 144000000000000;
auto y = string("hello");
auto z = y + ", world";
auto a = someFunc();

这里主要的便利还是减少了类型申明的冗余,不过对于他人来说可读性会很差。比如auto x = 0;只会推断为int,可能会带来不必要的内存占用(比如范围只需要-128-127),或是溢出。如果需要使用STL的string也需要使用类名,不然会被推断为char数组。

最佳实践还是在能明显看出变量类型的时候使用,如容器的迭代器申明。

1
2
3
4
5

vector<int>::const_iterator ci = vi.begin();
for (auto i = vec.begin();i != vec.end();i++) {
std::cout << (*i) << std::endl;
}

不过还是有坑,配合新出的for loop时注意引用的问题,默认是一个拷贝。

1
2
3
for (auto &i : vec) {
i++
;

}

decltype自动化推导

1
2
3
typedef decltype(someFunc()) ITER
auto a = someFunc(); // 比如返回值为vector<int>::const_iterator
decltype(a) b; // 自动推导为和a一样的类型

看到定义函数指针的用法,比较实用

1
2
3
4
5
6
7
8
9
10
11
12
int myfunc(int a){
return a;
}
decltype(&myfunc) pfunc = myfunc;

int main(int argc, char *argv[])
{

std::cout << (*pfunc)(0) <<std::endl;
pfunc = [](int a){return a+1;};
std::cout << (*pfunc)(0) <<std::endl;
return 0;
}

初始化语法

1
2
3
4
5
6
7
8
9
C c {0,0}; //C++11 only. 相当于: C c(0,0);

int* a = new int[3] { 1, 2, 0 }; /C++11 only

class X {
int a[4];
public:
X() : a{1,2,3,4} {} //C++11, member array initializer
};

看完上面几个例子,似乎很多类型的初始化都可以使用{}来统一了。
不过Manager指出其实只是新增加了new int[3]{1,2,3};这一类的,其他的方法是为了大一统加上的。这样看来,初始化的方法的确比较混乱了。

1
2
void foo(C c){}
foo({0,0});

其实可以这样写,进行自动化推导后初始化传入的结构体。不过不太易于阅读。

1
2
3
4
vector<string> vs={ "first", "second", "third" };
map<string, string> singers =
{ {"Lady Gaga", "+1 (212) 555-7890"},
{"Beyonce Knowles", "+1 (212) 555-0987"}};

适合进行容器初始化。以前map的初始化不友好。

map和表达式配合,有一种Javascript的感觉。

1
2
3
4
5
6
7
8
9
10
11
12
void handler(int a){}
int main(int argc, char *argv[])
{

map<string, decltype(&handler)> handlers =
{
{"ori", [](int a){std::cout << a << std::endl;}},
{"plusone", [](int a){std::cout << a+1 << std::endl;}}
};

(*handlers["ori"])(1);
return 0;
}

明晰语义的改动

delete/default修饰

我们经常需要单例模式需要private构造函数,可以使用delete告诉编译器不生成默认的构造函数。
不过可以申明为private,似乎不是特别的必要。

下面例子是一个比较好的实践:

1
2
void f(int);
void f(double) = delete;

明晰了使用时不能编译器遇到传入double的情况,不会自动做类型转换了

nullptr,有类型的NULL

NULL在函数重载时会产生歧义,导致具体逻辑得看编译器实现了。

1
2
3
4
5
6
void f(int); //#1
void f(char *);//#2
//C++03
f(0); //二义性
//C++11
f(nullptr) //无二义性,调用f(char*)

final/override修饰

final的使用不用多说了。override我们当时讨论了很久其存在的必要性。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class A{
public:
virtual void f(int a){
std::cout << "in A" << std::endl;
}
};
class B : public A {
public:
virtual void f(int a) override {
std::cout << "in B" << std::endl;
}
};

int main(int argc, char *argv[])
{

A* p = new B();
p->f(1.1);
return 0;
}

上面例子中,最终调用到了B的f,不过如果A的f参数改为double,会由override产生编译错误。
如果不加的话,就会绕过B::f。

委托构造函数

1
2
3
4
5
6
7
8
class Student {
public:
Student(int i, string n): id(i), name(n){}
Student(): Student(0, "none"){}
private:
int id;
string name;
};

之前一直需要抽出公共的构造部分,有了委托后可以更加高效,因为在初始化列表中便初始化成员了。

新特性

Lambda表达式

其实在上文中已经使用过了一些,形如:

1
2
[闭包捕捉](参数列表) -> 返回值类型 {函数体}
[](int a, double b) -> double {return a+b;}

闭包是指在表达式中可以直接访问到表达式创建时的上下文中的变量,这样可以将特定的参数封在表达式内,调用时更加简洁。
在Javascript中,我们可以直接闭包。C++中区分了传值和传引用,所以我们必须通过[]来显式地捕捉外界的变量。
规则比较简单,[=]便是传值所有使用的外界变量;[&]传引用;[&,x]表示x传值,其他传引用;[=,&y]表示y传递,其他传值。

1
2
3
4
int a = 100, b = 10;
// auto 在这里很方便,->double也可以省去,可以被编译器自动推导
auto ff = [=](int x, double y) ->double {return a+b+x + y;};
printf("%f\n", ff(1, 2.2));

在使用标准库的高阶函数时,便可以使用函数表达式,省去了函数的申明和需要闭包的变量之前不方便传递的问题。

1
2
3
4
int sum = 0;
vector<int> nums{1,2,3,4,5};
for_each(nums.begin(), nums.end(), [&sum](int a){sum+=a;});
printf("%d\n", sum);

Javascript中非常常见的立即调用表达式。

1
2
[] { printf("Hi\n"); } ();
[](int i) { printf("Hi%d\n", i); } (100);

右值引用

详细的介绍可参考这里,写得很详细。

这个右值引用应该还是很好的特性。不过需要使用者明白自己究竟在做什么,主要是减少了同类中的一些冗余的拷贝过程。
不过我们当时的争论在于如果只是使用指针,也可以达到同样的效果,不过这里便没有类的封装性了,标准库也没办法通过move告知进行内部成员的移动。不过使用智能指针应该是可以达到同样的效果。
当时还认为如果内部变量是new获得的,应该自行管理不能传递,不过我认为这里的管理都是在同类型中间进行,传递也只是同类的实例传递给另外一个同类,所以管理是封闭在类的内部的,是符合自行管理new创造的对象的。

元组

可以用来返回多个返回值。

1
2
3
4
5
6
7
8
9
typedef std::tuple< int , double, string       > tuple_1;
typedef std::tuple< char, short , const char * > tuple_2;
int main(int argc, char const *argv[])
{

tuple_1 t1;
tuple_2 t2 {'X', 2, "Hola!"}; // ()或是{}初始化都可以,不过建议使用新的{}统一初始化
t1 = t2 ; // 第三个字串元素可由'const char *'隐式转换
return 0;
}

摘自Wikipedia。

增强的标准库

并发库(线程、锁、原子操作)

正则表达式库

通用智能指针

散列表

随机数生成

变长参数模板

多态函数对象包装器

其他

最近希望做到动态的、有不同函数签名的函数的动态调用,后来只能使用switch case实现。
看到C++17标准中有一个invoke,似乎是动态地通过参数列表调用一个callable对象,也许以后还会加入GC和reflect。

更多改动可参考Wikipedia
Coolshell

hackshanghai

什么是Hackathon?

产品/服务开发需要耗费大量的人力,但经常一个很好的商业模式或是产品,都萌发自一两个人的小小想法,短时间内拿出一个Prof of Concept的原型,去证明你的想法,发现问题非常重要。

Hackathon就是程序员集中在一起,快速进行ideas 2 reality的活动,并且相互交流的过程。它通常由一个主办者发起,有大量的赞助商支持,通过严格的条件,比如限定24小时完成,限定方向和开发平台,来筛选出优秀的想法和开发者。当然,作为回报,Hackathon通常有优厚的奖品或是实习机会来吸引活跃的程序员。

应该来说每人都会经历自己的Hackathon,这个并不仅仅是软件行业。当你产生一个想法,并且你特别希望bring it to reality的时候,一次Hackathon就开始了。时间有长有短,可以是任何事情。

这一过程中的各个阶段:初期想法的产生,具体化细节,寻找合作伙伴,交流,推广,获取到别人的支持,反思评价都很有意思。其中还夹杂着很多情绪,兴奋,喜悦,妥协,各种压力,都会在短短的限定时间内爆发。走下去不放弃是必须的,当然这并不包括发现自己的想法不能实现,如果是这样,那你至少证明了它没有意义,或是有几个世界级的瓶颈。

所以大家都应该抓住机会参加一次Hackathon,能从这一过程和各种人身上学到很多,也能接触到很多新的平台和技术。

Hack Shanghai 2014

2014年的11月初,我和颢神、苗姐、冰神等在参加完软创大赛后,直接飞来上海,和姚神碰面参加Hack Shanghai 2014。想法在去之前有所讨论,但仍然不够清晰,是从我一个小小的技术尝试萌发的。

我们希望提升现有的网页视频播放的体验。通过定制化的界面,用户看到的是一个聚合好的视频库,资源来自各大视频网站。再通过智能手机充当遥控器,你便可以舒服得靠在沙发上,通过滑动屏幕、挥动手机进行视频的,进度拖动等操作。

经历了24小时的鏖战(睡了不到三小时),我们达到预期的效果了。姚神交互界面非常酷炫,颢神将各大视频站的资源破解使用,苗姐制作了美工并且准备了制胜法宝——PPT,我则开发Android上的遥控器,将触屏和加速度的数据处理后发到浏览器中控制视频。

第一次通宵熬夜,早上六七点时异常的难受。不过姚神一夜没睡,仍在战斗。随着代码合并,产品成型,效果超出了我们的预期。
产品展示环节我们站在自己的Booth旁边,不断向旁人展示我们的应用,其他组的产品创意也很好,有利用pebble手表进行阅读的应用,通过不断改变显示的文字,人眼可以在一个小屏幕上面舒适地阅读文章。
还有利用Leap Motion做厨房教学应用的点子,在hackathon上,很多硬件产品可以租借,利用一个全新的平台做出应用,这是开发商非常希望看到的。

被选入前九后,也就意味着我们要在评委和所有的250个Hacker面前再展示一次。姚哥进行了产品的阐述,我在一旁demo,效果很赞,评委们也很有兴趣地问了我们一些技术实现细节。

hackshanghai

hackshanghai

打开youku就能看到一个更好的视频界面。视频浏览页面,可以左右滑动。

上海纽约大学的确让第一届Hackathon Shanghai成为了现实,中国和外国志愿者们都非常热心,
最后我们拿到了很不错的奖品,一行五人,去人民广场开心地吃了顿日料,返程回校。

Hack Shanghai 2015

由于正好在上海实习,我便和冰神、火只和苗姐组队,再一次踏上hackathon之旅。

这次前期我们设想了很多Leap Motion的应用。

  • 进行数据可视化的互动,将一份金融或是教育领域的数据进行可视化,通过Leap Motion,利用手来与数据进行三维的互动,并且多人协同,可以共同编辑一份数据。
  • 或是用两根手指进行足球对抗赛,两只手指可以控制前进、后退或是射门,另外一只手可以控制奔跑的方向,也是可以在网页上进行多人的对抗。
  • 或是两根手指滑雪,进行神庙逃亡等等。

还有一些稀奇古怪的想法,

  • 利用Leap Motion教会你正确的刷牙姿势。不过一个Leap Motion似乎难以进行脸和手的相对位置的定位,由于手头没有Leap Motion,以后可以试试是否可行。我脑洞一开,觉得可以弄棍子穿着Leap Motion进行模拟刷牙,这样它捕捉的人脸的一部分应该足以计算出他们的相对位置。不过也难以进行尝试和实现。
  • 多人工作时候的协作,需求还不太明确。
  • 用头部姿态玩一些汽车的姿态控制游戏(画面太美)。
  • 任意网页弹幕。通过架设一个私人服务器,让在多地的相互认识的人能愉快的在同一个网页上弹幕,可以是小说,视频等等,还可以同步播放进度,发射弹幕聊天,自动进行鼠标位置的告知,对某些网页内容进行涂鸦,一个很好的应用便是异地的情侣,他们可以共同观看一个电影等,当然还可以加上实时的语音功能,不过得过滤掉网页上的媒体声音。

开幕式后,我们便继续讨论比较中意的Leap Motion应用,可是后来并没有借到Leap Motion。后来发现图灵机器人提供的Api还比较有趣的,它号称人工智能,能够进行情感的理解和表达。

我们最终做了Robot T,想做一个Web浏览器上的Google Now on Tap,
大致想法是让图灵机器人阅读你正在阅读的内容,像是一个你信任的好朋友站在身边,和你一起浏览。

hackshanghai
hackshanghai
hackshanghai
hackshanghai
比如知乎闲逛的时候,它会对你关注的问题主动地提出自己的看法,也许能惊艳到你。

hackshanghai
或者你滑词提示它阅读一些内容。

hackshanghai
他会在你午餐时间,基于你浏览的餐馆信息,向你推荐菜谱和餐馆。

hackshanghai
告示天气变化。

还有比如在你进行理财的时候,主动告诉你一些你可能会感兴趣的经济信息。

不过图灵机器人很多方面做得还是不如百度小度机器人,希望他们继续努力吧。

实现过程同样艰苦,我们奋斗到三点,终于熬不住去三楼找了个沙发躺了四个小时。早上起来的时候各种不好,不过还好没喝红牛,苗姐提示它含有激素。

本次的演示只有三分钟,三个外国教授进行评价。应该来说评委对我们的产品思想比较感兴趣,不过由于图灵机器人只能理解中文,我们只做了中文知乎和工商银行的网页的自动阅读和提示。

实现过程中我也有所失误,好长时间没写UI,抓耳挠腮写不出好看的,还好火只后期帮忙。
还有就是过于重视实现了,展现效果却比较差。使用者无法瞬间get这个应用的point。
和外国教授交流的时候也深深感受到了自己口语的拙技。

本次还看到了几个不错的项目。

  • 两人协同的平衡球游戏。看了知乎回答,的确24小时实现不简单。两个人协作让一个小球滚入洞中。你可以把一个小球从一个手机倒入另一个。由于手机的相对位置还可以移动,他们需要解决利用两个手机的传感器进行相对位置的计算。Web实现,微信分享链接就能一起玩,关卡设置花样很多,如果推广一下能火。
  • 一个利用Leap Motion让很多传感器上方的区域变成可点击的区域。比如上面摆着几本书,便可以选中其中一本。不过其实有点杀鸡用牛刀了。他们对使用场景还进了商业化的设想。团队四人来自清华,队长非常积极和健谈。和上海纽约大学的一位多媒体实验室的老师谈一些人机交互的技术。老师拿出了一个Project Tango设备进行展示。队长口语非常不错。
  • 还有一个是一个Chrome扩展,能在你百度搜索一些电影的时候,将背景变为trailer进行展示,效果很震撼,估计百度前段要哭了。不过视频的加载需要时间,内容需要提前分发到浏览器本地缓存。他们还做了很多的商业设想,我很佩服,虽然短时间内没办法全部实现展示,但这个意识非常好。

今天看了相关的知乎问题,共同经历了过hackathon,通过其他Hacker的心路历程,我们也能学到很多。
http://www.zhihu.com/question/37271008