谈谈分支与合并

branch
对于开发人员来说,经常会碰到这样的难题:某一个特性A正在开发之中,突然有了新的需求特性B,或者有紧急的bug C需要修改。如果B或C的重要性排在A之后,那么没有问题,等A开发完上线之后再来开发B或者C就可以了。但是通常情况是B和C的重要性要高于A。这种情况下,已经开发了一半的A特性的代码该何去何从?

branch_simple
代码目录拷贝

最简单的办法是把当前A特性的代码拷贝一份出来,放到别的目录下面,然后再修改原来的代码以支持B特性或者解决C bug,等这个版本改完后再把之前拷贝出来的A特性代码合并回去。但是拷贝出来的这份代码是不受版本控制的,中间的任何改动都没有记录,与使用版本控制的思想背道而驰。另外合并代码是个非常关键的操作,如果只有一个分支,手工合并问题也不大。但如果分支一多,就容易犯错了。

另一种办法就是使用版本控制软件自身的分支功能。
继续阅读谈谈分支与合并

布隆过滤器(Bloom Filter)简介

如果在系统中需要判断某个数据是否存在于集合中,一种可能的办法是把每个数据都存在容器中,然后再来查询,例如使用std::map。但有些情况下我们只关心数据存在与否,而并不关心数据本身。这种方法就会造成空间的浪费,影响系统整体的性能。在这种场景下,布隆过滤器则是一种很高效的办法。

布隆过滤器是一种非常节省空间的概率性数据结构。它可以非常快速地告诉你两个结果,“数据有可能存在于集合中”或者“数据一定不存在于集合中”。但布隆过滤器不是万能的,“Everything comes at a price”,为了获得查找的高性能,必须付出一些代价。很明显,布隆过滤器的结果并不那么精准。所以布隆过滤器只适用于那些可以容忍False Positive,但坚决不容忍False Negative的场景。比如,HBase中就使用了bloom filter来优化系统对硬盘的读取[1]

布隆过滤器的数据结构就是一个m位的bit数组。另外有k个不同的hash函数,其中每个函数会将输入的内容映射到bit数组的其中一位上。剩下的事情就很简单了,如果是新插入一条数据,只要把内容传给k个hash函数,得到k个位置,然后将数组中对应位置为1。如果是查询某个数据是否存在,则把数据内容做k个hash,得到对应的bit位,如果bit数组中这些位上的值有一个为0,那么这条数据肯定不存在。如果全部为1,则说明有可能已经存在。

Bloom_filter1

上面是布隆过滤器的示意图,可以看到这个布隆过滤器的bit数组有18位,另外有3个hash函数。

至于是否在实际系统中使用,还是要看对应的场景需求。参考链接中也有实际应用场景的例子[2]


参考

  1. Quora – How are bloom filters used in HBase?
  2. Bloom filter usage
  3. Bloom Filters by Example
  4. What is the advantage to using bloom filters?

swift closure capture list

最近用swift写app,发现很多特性都很有意思,并且很实用,比如closure, Optional类型等等。closure可以看做是objc中的block,c++和python中的lambda,熟悉函数式编程的同学可以很快上手。

在进行异步的网络调用时,需要把closure作为参数传递给执行网络请求的函数。比如NSURLSessionDataTask,需要把网络请求执行完毕之后的逻辑写在completionHandler里面。

很多时候我们需要在UIViewContronller里面来执行网络请求,请求完成后再更新ui上的数据,那么这里的closure中肯定会访问到self.someProperty或者self.someMethod()。这样closure本身就有一个对self的强引用(strong referrence)。另外,closure在swift中跟class一样,也是引用类型(referrence type)。这样一来closure和self就互相引用,产生strong referrence cycle。closure和self都无法被deallocate,造成资源的浪费。

为了解决这个问题,就要用到closure的捕获列表。语法如下:

捕获列表中可以使用两种类型:unowned和weak。因为一开始没有弄清楚这两种的区别,直接在代码中使用了unowned self,造成app在运行过程中出现“attempted to retain deallocated object”错误。

这段代码中第29行可以看到,在closure中访问self时并没有判断self是否还存在。所以当网络请求执行完再更新ui的时候就会出现“attempted to retain deallocated object”。unowned和weak的一个区别在于weak self是optional类型。这样一来就可以利用optional chaining来做一个保护机制。正确的实现代码如下:

这里的29行使用了swift中的if let结构,如果photoView是有值的,则继续执行后面的逻辑,否则会走到else分支中。这样便很好的解决了上面的问题。

参考:

  1. StackOverflow – Shall we always use [unowned self] inside closure in Swift?
  2. Automatic Reference Counting
  3. Swift Optionals: When to use if let, when ? and !, when as? and as

机器学习入门

2015年年初,因为工作接触到了机器学习(Machine Learning)。最近一直在学习,算是一只脚跨进了机器学习的大门。与之前积累的技能比较,机器学习更偏向于学术,学习难度比偏向于工程方面的技术大的多。要想掌握机器学习,线性代数,概率与统计,积分这些都是必不可少的。学生时代因为年少无知,看不到这些课程存在的意义,所以根本不放在心上,现在后悔已经来不及了。没办法,“出来混迟早都要还的”,只能看公开课重新拾起来了。这也算对自己的一个挑战,希望可以在新的领域有所突破。

什么是机器学习?

Machine learning is actually a software method. It’s a way to generate software. So, it uses statistics but it’s fundamentally… it’s almost like a compiler. You use data to produce programs.

通过最近的学习,已经有了对机器学习的简单认识。机器学习就是利用一些算法为已有的数据建立模型,得出数据的规律,然后根据新的输入数据做出预测或者决策。举几个例子:根据房产交易的数据(房屋面积、所处地段、是否学位房、成交价格等等),估算出另一套房子大概的售价;根据最近几年某只股票的走势和其间相关的事件预测未来走势;根据肿瘤细胞的特征评估是良性还是恶性的。最贴近生活的几个例子:根据你喜欢的音乐为你推荐新的音乐(网易云音乐做的最好);微博里面根据你的关注为你推荐新的用户;社交平台根据某一用户所有的行为数据预测什么广告最容易获得他的点击(qq,微信)。看到这些应用,相信你也会像我一样深深的被它迷住。如果看到这些你还没有心动,可以再看看这篇文章:Data Scientist: The Sexiest Job of the 21st Century

资源

近期积累了不少机器学习相关的教程和资料,整理出来发在这里。其实关于机器学习的资料有太多太多,以至于一开始的时候都出现了选择恐惧症,不知道该从什么地方开始。好在后来能够静下心来从最简单的开始。

公开课
1. 强烈推荐Coursera上的Machine Learning。这是机器学习的入门课程,作者是Andrew Ng,也是coursera的创始人。如果连这门课都没看过,实在是不好意思跟别人说自己懂机器学习。别看这门课简单,想从头到尾认真完成真不是件简单的事情,不仅仅是把视频看完,而且要把所有习题和编程题做完。我从2月份开始,一直看到现在(5.23),还剩下最后两章没有看完。但是已经超过了课程的deadline,拿不到课程certificate了。Andrew Ng讲得深入浅出,只要能仔细完成,机器学习就算是入门了。

2. 统计学的课程也是必须的。这里比较推荐Statistical Learning。Statistical Learning的作者是大名鼎鼎的The Elements of Statistical Learning(ESL)这本书的作者。ESL的难度比较大,不太适合入门,所以作者又写了另外一本面向初学者的An Introduction to Statistical Learning(ISL)。ISL可以作为课程的配套书籍来看,事半功倍。更重要的是ISL是免费的。

Coursera的ML公开课看的差不多之后,可以写点代码实际操练起来了。scikit-learn是一个很重要的机器学习库。本文题图就是sklearn官方给出的如何选择合适的机器学习算法的roadmap。sklearn的学习我推荐An Introduction to scikit-learn: Machine Learning in Python这篇教程,里面给出了如何用sklearn实现常用机器学习的算法。

其他还有些非常重要的资源:
1. Datatau:大数据界的Hacker News
2. Reddit的Machine Learning频道
3. Quora的Mechine Learning话题,另外推荐关注William Chen

也许用不了多少时间,奇点就会到来。所以能多了解点机器学习的事儿也是好事,加油!

objective-c protected instance variable

最近在做斯坦福公开课cs193p的课后练习Machismo,其中用到了类的继承。

[ccb_objc]
// GameViewController.h
@interface GameViewController : UIViewController
@property (nonatomic) Game *game;
@property (strong, nonatomic) IBOutletCollection(UIButton) NSArray *cardButtons;
@property (weak, nonatomic) IBOutlet UILabel *scoreLabel;
@property (weak, nonatomic) IBOutlet UILabel *messageLabel;
@end

// CardGameViewController.h
@interface CardGameViewController : GameViewController
@property (nonatomic) CardMatchingGame *game;
@end
[/ccb_objc]

[ccib_objc]CardGameViewController[/ccib_objc]是[ccib_objc]GameViewController[/ccib_objc]的一个子类。因为在子类中需要初始化[ccib_objc]@property (nonatomic) CardMatchingGame *game[/ccib_objc],所以就要在子类中访问基类中的instance variable,也就是[ccib_objc]_game[/ccib_objc]。如果这样尝试的话,编译器会给出一个错误。因为[ccib_objc]_game[/ccib_objc]是基类的private instance variable,子类是无法访问的。那如何才能将这个instance variable变为protected?只要将[ccib_objc]_game[/ccib_objc]在interface中声明即可。
[ccb_objc]
// GameViewController.h
@interface GameViewController : UIViewController {
Game *_game;
}
@property (nonatomic) Game *game;
@property (strong, nonatomic) IBOutletCollection(UIButton) NSArray *cardButtons;
@property (weak, nonatomic) IBOutlet UILabel *scoreLabel;
@property (weak, nonatomic) IBOutlet UILabel *messageLabel;
@end
[/ccb_objc]
在interface中声明的变量,默认都是[ccib_objc]@protected[/ccib_objc]。如果想定义为其他类型,可以使用[ccib_objc]@public[/ccib_objc]和[ccib_objc]@private[/ccib_objc]等关键字。这点和C++还是类似的。更多详细内容请参阅下面的资料。

参考

  1. How are declared private ivars different from synthesized ivars?
  2. Are synthesized instance variables generated as private instead of protected?
  3. Objective-C Access To Instance Variables
  4. Private properties, methods and ivars in Objective-C
  5. Objective-C Tuesdays: instance variables

redis sds数据结构的指针技巧

redis中没有直接使用char *,而是使用自定义的sds结构来表示字符串。sds的定义如下:
[ccb_c no_links=”true”]
typedef char *sds;
struct sdshdr {
int len;
int free;
char buf[];
};
[/ccb_c]
sdshdr中的len代表buf中字符串的长度(不包括结尾的’’),free代表buf剩余可用的长度。这种结构与char *相比有什么优点,请参考Redis设计与实现

上面看到sds这种类型就是char *,那sds和sdshdr有什么关系?先来看生成sds的函数:
[ccb_c no_links=”true”]
sds sdsnewlen(const void *init, size_t initlen) {
struct sdshdr *sh;

if (init) {
sh = zmalloc(sizeof(struct sdshdr)+initlen+1);
} else {
sh = zcalloc(sizeof(struct sdshdr)+initlen+1);
}
if (sh == NULL) return NULL;
sh->len = initlen;
sh->free = 0;
if (initlen && init)
memcpy(sh->buf, init, initlen);
sh->buf[initlen] = ‘’;
return (char*)sh->buf;
}
[/ccb_c]
每当生成sds的时候,会先生成sdshdr,然后返回[ccib_c]sdshdr->buf[/ccib_c]。原来sds就是[ccib_c]sdshdr->buf[/ccib_c],sdshdr的意思应该是“sds header”。sds相关的函数大多以sds作为参数的类型,而不是sdshdr,当需要用到sdshdr的时候,redis使用了一个技巧,以便快速的通过sds得到sdshdr。以sdslen这个函数为例:
[ccb_c no_links=”true”]
static inline size_t sdslen(const sds s) {
struct sdshdr *sh = (void*)(s-(sizeof(struct sdshdr)));
return sh->len;
}
[/ccb_c]
通过将sds的地址减去sizeof(struct sdshdr),便得到了sdshdr的地址。redis的文档里面已经解释了这个技巧。假如我们通过[ccib_c]sdsnewlen(“redis”, 5)[/ccib_c]生成了一个sds,其sdshdr的地址为sh,那么像下图所示:
[ccb_c]
———–
|5|0|redis|
———–
^ ^
sh sh->buf
[/ccb_c]
[ccib_c]sh = sh->buf – sizeof(struct sdshdr)[/ccib_c]。看到这里也许会有疑问,[ccib_c]sizeof(strcut sdshdr)[/ccib_c]不应该是[ccib_c]sizeof(int) + sizeof(int) + sizeof(char *)[/ccib_c]吗?C99标准中有如下规定:

As a special case, the last element of a structure with more than one named member may have an incomplete array type; this is called a flexible array member. In most situations, the flexible array member is ignored. In particular, the size of the structure is as if the flexible array member were omitted except that it may have more trailing padding than the omission would imply.

However, when a . (or ->) operator has a left operand that is (a pointer to) a structure with a flexible array member and the right operand names that member, it behaves as if that member were replaced with the longest array (with the same element type) that would not make the structure larger than the object being accessed; the offset of the array shall remain that of the flexible array member, even if this would differ from that of the replacement array. If this array would have no elements, it behaves as if it had one element but the behavior is undefined if any attempt is made to access that element or to generate a pointer one past it.

sdshdr最后一个成员是未完整定义的数组类型,这种结构体的定义方式使buf成为可变数组成员。内核中也有flexible array member的应用:
[ccb_c]
struct bts_action {
u16 type;
u16 size;
u8 data[0];
} __attribute__ ((packed));

struct bts_action *var = kmalloc(sizeof(*var) + extra, GFP_KERNEL);
[/ccb_c]
这样就减少了kmalloc的调用次数。这就是通过将buf的地址减去sizeof(struct sdshdr)可以得到sdshdr地址的原因。

参考

  1. Flexible array members in C – bad?
  2. Size of a struct with two void pointers is 4?
  3. Hacking Strings

django+tornado

Django的部署方式有很多种:WSGI,FastCGI等等,当然最主流的还是WSGI。比如官方文档中推荐的django+apache+mod_wsgi, django+gunicorn,django+uWSGI等等。那么什么是WSGI?

其实这个问题也困扰了我很久。每当我想研究一下WSGI到底是何方神圣的时候,google给出的答案就是pep 3333,不过这篇像论文一样的pep 3333比较晦涩难懂,一直都没搞明白。直到后来发现了这篇文章。和其他的文章不一样,文章没有开头就讲WSGI是什么,反而开始就说WSGI不是什么:

What WSGI is not: a server, a python module, a framework, an API or any kind of software. What it is: an interface specification by which server and application communicate.

看到这里心中无比的敞亮,原来WSGI就是一种标准:application和server之间交互的标准。application就是通常所说的framework,server则接受客户端的请求并将请求发给application处理,然后将application的应答返回给客户端。现在应该明白,之前说的apache+mod_wsgi, gunicorn, uWSGI都是扮演WSGI Server的角色,只要兼容WSGI的server都可以用来和任意的python web framework搭配。

今天的主角是Tornado。它包含两部分:一个framework和一个server。由于tornado使用了异步非阻塞的IO模型,所以特别适用于长轮询的场景,性能比较强大。今天主要用tornado的http server来部署django应用。

tornado的wsgi模块包含了两个类WSGIApplication和WSGIContainer。WSGIApplication是把tornado作为framework,用其他的WSGI server来驱动。WSGIContainer则是我们需要的,把其他web framework的应用封装一下,以便在tornado server上运行。
继续阅读django+tornado

使用el-get管理emacs包

el-get是emacs的一个包管理器。你可以把它当作是emacs的synaptic、yum或者pacman,功能强大,用起来也十分方便。通过它你可以安装和管理各种elisp的包。对于我来说,el-get最大的功能在于,当你有多台机器时,仅仅需要一个.emacs文件就可以保证多个机器上的elisp包一致,而不是每个机器都需要手动管理。想想看,没有el-get的话,当你重装系统之后需要花多少时间来安装各种elisp包吧。有了el-get之后,只要一个.emacs文件,当你第一次打开emacs的时候,各种包已经开始默默的下载和安装了,完全不用你操心。这是一件多么美好的事啊。

el-get对软件包的描述放在recipe文件中,包括安装方式、url、安装完之后的操作,以及软件包的基本配置。也就是说当你用el-get安装完某个包之后,自己的.emacs文件基本上不用修改,新的包就可以用了。el-get支持多种安装方式,可以直接从github clone,也可以从指定的url下载回来然后解压,另外还支持ELPA,cvs,bzr,apt-get,pacman等等。最强大的地方在于你可以自己定义recipe,如果默认的recipe文件中没有你需要的包,那么你完全可以自己写一个。如果你在github上面fork了el-get,可以给作者提交pull request,请求将自己定义的recipe加入默认的recipe中。这样如果下次有别人需要用到这个包,你的recipe就派上用场了。

安装el-get非常简单,只要打开你的emacs,切换到scratch buffer,复制下面这段代码,然后在最后一个括号后面C-j就可以了。
[ccb_lisp]
(url-retrieve
“https://raw.github.com/dimitri/el-get/master/el-get-install.el”
(lambda (s)
(let (el-get-master-branch)
(goto-char (point-max))
(eval-print-last-sexp))))
[/ccb_lisp]

安装好el-get后,配置文件里面加入下面的内容
[ccb_lisp]
(setq
el-get-sources
‘((:name asciidoc
:type elpa
:after (progn
(autoload ‘doc-mode “doc-mode” nil t)
(add-to-list ‘auto-mode-alist ‘(“\.adoc$” . doc-mode))
(add-hook ‘doc-mode-hook ‘(progn
(turn-on-auto-fill)
(require ‘asciidoc)))))

(:name buffer-move ; have to add your own keys
:after (progn
(global-set-key (kbd ““) ‘buf-move-up)
(global-set-key (kbd ““) ‘buf-move-down)
(global-set-key (kbd ““) ‘buf-move-left)
(global-set-key (kbd ““) ‘buf-move-right)))

(:name smex ; a better (ido like) M-x
:after (progn
(setq smex-save-file “~/.emacs.d/.smex-items”)
(global-set-key (kbd “M-x”) ‘smex)
(global-set-key (kbd “M-X”) ‘smex-major-mode-commands)))

(:name lisppaste :type elpa)))

(setq my-packages
(append
‘(cssh el-get switch-window escreen vkill xcscope color-theme color-theme-railscasts color-theme-tomorrow yasnippet python-mode python-pep8 pymacs rope ropemacs ropemode pylookup haskell-mode anything helm emacs-w3m auto-complete browse-kill-ring sr-speedbar session popup markdown-mode xml-rpc-el)
(mapcar ‘el-get-source-name el-get-sources)))

(el-get ‘sync my-packages)
[/ccb_lisp]
上面的el-get-sources变量是自定义的recipe,my-packages则是需要安装的包。只要把包的名字写在my-packages里面,下次启动emacs的时候el-get就会自动安装。

使用el-get就是这么简单。但是在使用的过程中发现el-get还有些不完美的地方。比如用el-get安装color-theme这个插件的时候,总会提示下载的color-theme.tar.gz无法解压。经过一番调查发现原来是el-get的http-method有问题。el-get下载的color-theme.tar.gz总会比用wget下载的少几个字节。http-method的实现是先用url-retrieve把文件拉到当前的buffer中,再用write-file将当前buffer的内容写入文件。问题就出在write-file这里。write-file默认会对buffer进行一些编码的转换,结果导致丢掉了几个字节。修改的方法也很简单,只要将write-file替换为write-region即可。发现这个bug之后在github上给作者提交了pull request,已经被合并了。之后再有人用el-get就不会碰到这个问题了。

学习elisp

对我来说,Emacs已经成为日常用的最多的编辑器了,vi则只有在ssh的时候才会用到。不过emacs可是很需要调教的,如果没有好好配置,基本上处于不可用的状态。关于目前使用的emacs配置,改天会再详细写出来。今天只是分享一个自己写的函数。

我很庆幸,现在的我能够沉下心来去研究elisp这么神奇的语言。虽然elisp的用途很狭隘,但它绝对会对提升你的生产力产生巨大的帮助。emacs我用了很久,大概跟我用linux的时间一样长。但是从2012年开始我才算真正入了emacs的门。以前只是在网上搜罗各种配置文件,安装各种插件。但是配置却看不懂,做不到自己定制,只是简单的依葫芦画瓢。配置不适用也不知道该如何下手解决。

随着使用的深入,现在慢慢学会了如何解决遇到的emacs问题。搞定了以前一直很想解决的bug。有些bug,其实是emacs的某些feature没有配置好。比如之前python-mode中有时出现C-x C-f会卡死的情况,最后发现原来是网上广泛流传的python-mode配置中[ccib_lisp](setq ido-use-filename-at-point ‘guess)[/ccib_lisp]这一句,导致打开文件的时候会调用python-mode-path函数。只要把这句注释掉即可。再比如python-mode中M-b的跳转总是不正确,在其他的major mode中M-b总是会把“[ccib_lisp]_[/ccib_lisp]”当作单词的分隔符,但python-mode中却不会。这点实际使用中会造成很大的困扰,因为不仅和其他的major mode行为不一致,即便在python-mode中M-b和M-f的行为也不一致。后来到python-mode的项目主页上准备提交这个bug,结果发现已经有人提过了,原因是“[ccib_lisp]_[/ccib_lisp]”在python-mode的syntax table中是属于word类而不是symbol类。现在python-mode中已经有了一个自定义的变量[ccib_lisp]py-underscore-word-syntax-p[/ccib_lisp],只要把此变量的值设为nil即可。

解决了前面的几个bug之后,开始想自定义几个函数已满足自己的需求。以前用惯了vim的yy复制一行的功能,现在emacs里面没有,非常的不适应,这个操作如果自己做,需要按太多次键盘。先要C-a跳到行首,再C-spc设置标记,再C-e跳到行尾。于是开始翻阅elisp的文档。发现elisp的文档写的真是太人性化了,目录编排尤其合理,即便是我这种从来没用过的人也可以对着目录找到自己所需要的函数或者变量。下面是复制一行的函数
[ccb_lisp]
;;copy line
(defun copy-line ()
(interactive)
(kill-ring-save (save-excursion
(back-to-indentation)
(point))
(line-end-position))
(message “line copied”))
(global-set-key “C-cC-y” ‘copy-line)[/ccb_lisp]
实现起来很简单,[ccib_lisp]kill-ring-save[/ccib_lisp]的描述是“Save the region as if killed, but don’t kill it”,即我们通常理解的“复制”。这个函数会接受两个参数,待复制区域的起始和结束位置。[ccib_lisp]save-excursion[/ccib_lisp]的作用是再执行完函数参数内的操作后,把光标移动到执行之前的位置。[ccib_lisp]save-excursion[/ccib_lisp]这段的作用是获得行的起始位置,略去前面的缩进。[ccib_lisp]line-end-position[/ccib_lisp]很明显就是获得行的结束位置。这样就实现了复制一行的功能,再把这个函数绑定一个快捷键C-c C-y即可。

现在终于体会到了emacs的强大。emacs的各种插件还不足以让他成为神器,最强大的地方在与用户可以很容易的自己定制所需要的功能,而不用依赖于别人开发的插件。难怪有人说emacs是伪装成编辑器的操作系统了。

编译器拷贝优化(copy elision)

Copy Elision是大多数编译器都会执行的一项优化。gcc的手册里面是这样描述的:

-fno-elide-constructors
The C++ standard allows an implementation to omit creating a temporary which is only used to initialize another object of the same type. Specifying this option disables that optimization, and forces G++ to call the copy constructor in all cases.

当需要创建一个只是用来初始化其他对象的临时对象时,编译器可以忽略此操作,不执行拷贝构造函数(copy-initialization),而是直接把临时对象创建在目标对象的空间上(direct-initialization),从而提高程序的性能。例如:
[ccb_c++]
class foo
{
public:
foo() {cout<<"ctor"<如何利用拷贝优化

假设我们需要写这样一个函数:
[ccb_c++]
int f1(const foo &a)
{
foo tmp(a);
do_something(tmp);

return 0;
}
[/ccb_c++]
为了避免改变参数,函数f1生成一份参数的拷贝,更改复制得到的对象。其实这样做并没有什么好处,既然复制操作无法避免,那就让它来的自然点好了。
[ccb_c++]
int f2(foo a)
{
do_something(a);

return 0;
}
[/ccb_c++]
与f1相比,f2的好处在于,当参数是右值的时候,拷贝操作被省略了。如果参数是左值,那么两个函数都会进行一次拷贝。

但有一点需要注意,如果函数将参数返回,那么RVO将不会起作用。
[ccb_c++]
foo f3(foo a)
{
do_something(a);

return a;
}
[/ccb_c++]
其实这也很容易理解,编译器在编译使用此函数的代码时,必定会使用两个地址,一个存放按值传递的参数,另一个存放返回值。在返回的时候,一定执行一次拷贝构造函数,将参数复制到返回值的地址上。更详细的解释参见Why is RVO disallowed when returning a parameter?

即使不能利用RVO,我们也可以将函数优化。一般而言,默认构造函数和swap操作会比拷贝构造函数更快,所以可以这样写:
[ccb_c++]
foo f4(foo a)
{
do_something(a);
foo tmp;
tmp.swap(a);

return tmp;
}
[/ccb_c++]

参考

  1. The Name Return Value Optimization
  2. C++: Exploiting Copy Elisions
  3. Why is RVO disallowed when returning a parameter?
  4. Is RVO (Return Value Optimization) guaranteed for all objects in gcc compilers?