刘未鹏|C++的罗浮宫

Knowledge sharing is the best reuse

原创 《C++0x漫谈》系列之:右值引用(或“move语意与完美转发”)(下) 收藏

C++0x漫谈》系列之:右值引用

或“move语意与完美转发”(下)

 

By 刘未鹏(pongba)

刘言|C++的罗浮宫(http://blog.csdn.net/pongba)

 

 

C++0x漫谈》系列导言

 

这个系列其实早就想写了,断断续续关注C++0x也大约有两年余了,其间看着各个重要proposals一路review过来:rvalue-referencesconceptsmemory-modelvariadic-templatestemplate-aliasesauto/decltypeGCinitializer-lists…

 

总的来说C++09C++98相比的变化是极其重大的。这个变化体现在三个方面,一个是形式上的变化,即在编码形式层面的支持,也就是对应我们所谓的编程范式(paradigm)C++09不会引入新的编程范式,但在对泛型编程(GP)这个范式的支持上会得到质的提高:conceptsvariadic-templatesauto/decltypetemplate-aliasesinitializer-lists皆属于这类特性。另一个是内在的变化,即并非代码组织表达方面的,memory-modelGC属于这一类。最后一个是既有形式又有内在的,r-value references属于这类。

 

这个系列如果能够写下去,会陆续将C++09的新特性介绍出来。鉴于已经有许多牛人写了很多很好的tutor这里这里,还有C++标准主页上的一些introductiveproposals,如这里,此外C++社群中老当益壮的Lawrence Crowl也在google做了非常漂亮的talk)。所以我就不作重复劳动了:),我会尽量从一个宏观的层面,如特性引入的动机,特性引入过程中经历的修改,特性本身的最具代表性的使用场景,特性对编程范式的影响等方面进行介绍。至于细节,大家可以见每篇介绍末尾的延伸阅读。

 

 

右值引用导言

 

右值引用(及其支持的Move语意和完美转发)是C++0x将要加入的最重大语言特性之一,这点从该特性的提案在C++ - State of the Evolution列表上高居榜首也可以看得出来。从实践角度讲,它能够完美解决C++中长久以来为人所诟病的临时对象效率问题。从语言本身讲,它健全了C++中的引用类型在左值右值方面的缺陷。从库设计者的角度讲,它给库设计者又带来了一把利器。从库使用者的角度讲,不动一兵一卒便可以获得“免费的”效率提升

 

 

完美转发

 

完美转发问题——不完美解决方案——模板参数推导规则——完美转发

 

动机

关于“完美转发”这个特性,其实提案N1385已经讲得非常清楚了,诸位可以直接去看N1385,如果实在还是觉得迷糊就再回来听我唠叨吧:-)

 

在泛型编码中经常出现的一个问题是(这个问题在实际中出现的场景很多,我们留到文章末尾再提,目前我们将这个特定的问题先提取孤立出来考虑):

 

如何将一组参数原封不动地转发给另一个函数

 

注意,这里所谓“原封不动”就是指,如果参数是左值,那么转发给的那个函数也要接受到一个左值,如果参数是右值,那么后者要接受到一个右值;同理,如果参数是const的,那么转发给的那个函数也要接受到一个const的值,如果是non-const的,那么后者也要接受到一个non-const的值。

 

总之一句话:

 

保持参数的左值/右值、const/non-const属性不变

 

听上去很简单吗?不妨试试看。

 

(不完美的)解决方案

假设我们要写一个泛型转发函数ff要将它的参数原封不动地转发给g(不管g的参数类型是什么):

 

template<typename T>

void f(/*what goes here?*/ t)

{

g(t);

}

 

上面的代码中,f的参数t的类型是什么?TT&const T&

 

我们一个个来分析。

 

Value

如果t的类型是T,即:

 

// take #1

template<typename T>

void f(T t)

{

g(t);

}

 

那么很显然,不能满足如下情况:

 

void g(int& i) { ++i; }

 

int myInt = 0;

 

f(myInt); // error, the value g() has incremented is a local value(a.k.a. f’s argument ‘t’)

 

即,不能将左值转发为左值。

 

Const&

如果t的类型为const T&,即:

 

// take #2

template<typename T>

void f(const T& t)

{

g(t);

}

 

则刚才的情况还是不能满足。因为g接受的参数类型为non-const引用。

 

Non-const&

那如果t的类型是T&呢?

 

// take #3

template<typename T>

void f(T& t)

{

g(t);

}

 

我们惊讶地发现,这时,如果参数是左值,那么不管是const左值还是non-const左值,f都能正确转发,因为对于const左值,T将会被推导为const UU为参数的实际类型)。并且,对于const右值,f也能正确转发(因为const引用能够绑定到右值)。只有对non-const右值不能完美转发(因为这时T&会被推导为non-const引用,而后者不能绑定到右值)。

 

即四种情况里面有三种满足了,只有以下这种情况失败:

 

void g(const int& i);

 

int source();

 

f(source()); // error

 

如果f是完美转发的话,那么f(source())应该完全等价于g(source()),后者应该通过编译,因为g是用const引用来接受参数的,后者在面对一个临时的int变量的时候应该完全能够绑定。

 

而实际上以上代码却会编译失败,因为f的参数是T&,当面对一个non-constint型右值(source()的返回值)时,会被推导为int&,而non-const引用不能绑定到右值。

 

好,现在的问题就变成,如何使得non-const右值也被正确转发,用T&f的参数类型是行不通的,唯一能够正确转发non-const右值的办法是用const T&来接受它,但前面又说过,用const T&行不通,因为const T&不能正确转发non-const左值。

 

Const& + non-const&

那两个加起来如何?

 

template<typename T>

void f(T& t)

{

g(t);

}

 

template<typename T>

void f(const T& t)

{

g(t);

}

 

一次重载。我们来分析一下。

 

对于non-const左值,重载决议会选中T&,因为绑定到non-const引用显然优于绑定到const引用(const T&)。

 

对于const左值,重载决议会选中const T&,因为显然这是个更specialized的版本。

 

对于non-const右值,T&根本就行不通,因此显然选中const T&

 

对于const右值,选中const T&,原因同第二种情况。

 

可见,这种方案完全保留了参数的左右值和const/non-const属性。

 

值得注意的是,对于右值来说,由于右值只能绑定到const引用,所以虽然const T&并非“(non-)const右值”的实际类型,但由于C++03只能用const T&来表达对右值的引用,所以这种情况仍然是完美转发。

 

组合爆炸

你可能会觉得上面的这个方案(const& + non-const&)已经是完美解决方案了。没错,对于单参的函数来说,这的确是完美方案了。

 

但是如果要转发两个或两个以上的参数呢?

 

对于每个参数,都有const T&T&这两种情况,为了能够正确转发所有组合,必须要2N次方个重载

 

比如两个参数的:

 

template<typename T1, typename T2>

void f(T1& t1, T2& t2) { g(t1, t2); }

 

template<typename T1, typename T2>

void f(const T1& t1, T2& t2) { g(t1, t2); }

 

template<typename T1, typename T2>

void f(T1& t1, const T2& t2) { g(t1, t2); }

 

template<typename T1, typename T2>

void f(const T1& t1, const T2& t2) { g(t1, t2); }

 

(完美的)解决方案

理想情况下,我们想要:

 

template<typename T1, typename T2, … >

void f(/*what goes here?*/ t1, /**/ t2, … )

{

  g(t1, t2);

}

 

填空处应该填入一些东西,使得当t1对应的实参是non-const/const的左/右值时,t1的类型也得是non-const/const的左/右值。目前的C++03中,non-const/const属性已经能够被正确推导出来(通过模板参数推导),但左右值属性还不能。

 

明确地说,其实问题只有一个:

 

对于non-const右值来说,模板参数推导机制不能正确地根据其右值属性确定T&的类型(也就是说,T&会被编译器不知好歹地推导为左值引用)。

 

修改T&non-const右值的推导规则是可行的,比如对这种情况:

 

template<typename T>

void f(T& t);

 

f(1);

 

规定T&推导为const int&

 

但这显然会破坏既有代码。

 

很巧的是,右值引用能够拯救世界,右值引用的好处就是,它是一种新的引用类型,所以对于它的规则可以任意制定而不会损害既有代码,设想:

 

template<typename T >

void f(T&& t){ g(t); }

 

我们规定:

 

如果实参类型为右值,那么T&&就被推导为右值引用。

如果实参类型为左值,那么T&&就被推导为左值引用。

 

Bingo!问题解决!为什么?请允许我解释。

 

f(1); // T&& 被推导为 int&&,没问题,右值引用绑定到右值。

f(i); // T&& 被推导为 int&,没问题,通过左值引用完美转发左值。

 

等等,真没问题吗?对于f(1)的情况,t的类型虽然为int&&(右值引用),但那是否就意味着t本身是右值呢?既然t已经是具名(named)变量了,因此t就有被多次move(关于move语意参考上一篇文章)的危险,如:

 

void dangerous(C&& c)

{

C c1(c); // would move c to c1 should we allow treating c as a rvalue

c.f(); // disaster

}

 

在以上代码中,如果c这个具名变量被当成右值的话,就有可能先被move掉,然后又被悄无声息的非法使用(比如再move一次),编译器可不会提醒你。这个邪恶的漏洞是因为c是有名字的,因此可以被多次使用。

 

解决方案是把具名的右值引用作为左值看待

 

但这就使我们刚才的如意算盘落空了,既然具名的右值引用是左值的话,那么f(1)就不能保持1的右值属性进行转发了,因为f的形参t的类型(T&&)虽然被推导为右值引用(int&&),但t却是一个左值表达式,也就是说f(1)把一个右值转发成了左值。

 

最终方案

通过严格修订对于T&&的模板参数推导原则,以上问题可以解决。

 

修订后的模板参数推导规则为:

 

如果实参是左值,那么T就被推导为U&(其中U为实参的类型),于是T&& = U& &&,而U& &&则退化为U&(理解为:左值引用的右值引用仍然是左值引用)。

 

如果实参是右值,那么T就被推导为U,于是T&& = U&&(右值引用)。

 

如此一来就可以这样解决问题:

 

template<typename T>

void f(T&& t)

{

  g(static_cast<T&&>(t));

}

 

想想看,如果实参为左值,那么T被推导为U&T&&U& &&,也就是U&,于是static_cast<T&&>也就是static_cast<U&>,转发为左值。

 

如果实参为右值,那么T被推导为UT&&U&&static_cast<T&&>也就是static_cast<U&&>,不像t这个具名的右值引用被看作左值那样,static_cast<U&&>(t)这个表达式由于产生了一个新的无名(unnamed)值,因而是被看作右值的。于是右值被转发为了右值。

 

扩展阅读

[1] Rvalue.Reference.Proposed.Wording(rev#3) (a.k.a. N2118)

[2] Impact of the rvalue reference on the Standard Library (a.k.a. N1771)

 

下篇预告

下篇会写variadic templates。然后介绍tr1::tuple的新版实现。

 

目录(展开C++0x漫谈》系列文章)

 

发表于 @ 2007年07月18日 20:50:00|评论(loading...)

新一篇: Generic Programming - What are you, anyway? | 旧一篇: 《C++0x漫谈》系列之:右值引用(或“move语意与完美转发”)(上)

用户操作
[即时聊天] [发私信] [加为好友]
刘未鹏
订阅我的博客
XML聚合  FeedSky
订阅到鲜果
订阅到Google
订阅到抓虾
刘未鹏的公告
除非特别声明,本站采用Creative Commons License许可。转载请保留作者、出处。非商业。

重要公告

本博客已经迁移至 http://mindhacks.cn ,此处保留作为镜像,但不保证一定同步更新所有内容。原订阅 http://blog.csdn.net/pongba/rss.aspx (原始 Feed) 的朋友请转为订阅永久 feed : http://mindhacks.cn/feed/

关于

我经常在 @TopLanguage | @Twitter | @Douban

《C++的罗浮宫》5年选集

——知识分享是最大的复用

下载地址:csdn资源频道|mediafire

讨论问题请到TopLanguage综合技术讨论组

TopLanguage

精彩言论@TopLanguage


pongba的共享阅读@Delicious


pongba@Twitter


pongba在读@豆瓣


gtalk/msn(邮件请发送到gmail邮箱)

pongba@gmail.com
pp_liu@msn.com

搜索(不要回车,点击Go)


pongba翻译的





这个Blog上都写了哪些东东

文章分类
收藏
C++
Andrei Alexandrescu
Andrew Lumsdaine
Bjarne Stroustrup
boost
C++ Standard Commitee
Doug Gregor
Hans J. Boehm
Jaakko Jarvi
Jeremy G. Siek
Matthew Wilson
newsgroups
boost.Developer
boost.User
comp.lang.c++.moderated
comp.std.c++
TopLanguage
Open Source
Ant
codeplex
Danga
Google AJAX Search API
Google Code Prettify
Google Web Toolkit
Hadoop
MS shared source initiative
notepad++
STLSoft
不认识的朋友们
Delphij
fatalerror99
flow with the life
Glacier
jimaxsoft
lifesinger@淘宝UED
Mr. 6
realazy
Robbin
SpiritEpic
TK
wuyizi
Yelz
丁丁虫
付翀
冰云
刘慈欣
卢昌海
吴欣安(atppp)
周爱民
和菜头
姬十三
守望轩
小花@BlogBus
林达华
浦宇平
白鸦
程化
罗浩|Startup Game
阮一峰
霍炬
飞之鸿
高远
鲍盛
机器学习/数据挖掘/信息检索/自然语言处理/认知科学/人工智能
AAAI
Apex
arXiv
Charles Kemp
Christopher Bishop
Christopher Manning
Cognitive Daily
Dan Jurafsky
David MacKay
ECML PKDD
Geoffrey Hinton
Herbert Simon
ICML
IJCAI
Jeff Hawkins
Jiawei Han
JMLR
Josh Tenenbaum
Larry Wasserman
Lucene
Marvin Minsky
MIT AI Lab
MIT Computational Cognitive Science Group
Mitchell Marcus
ML
NetLab
NIPS
Peter Norvig
Stanford AI Lab
Stanford NLP Lab
Stephen Boyd
Tom Mitchell
Trends in Cognitive Science
Vladimir Vapnik
Weka
Zhihua Zhou
技术
Coding Horror
High Scalability
Reddit
Stack Overflow
Steve Yegge
代码发芽网
淘宝UED团队
淘宝数据仓库团队
玩聚网
移山之道
其它
Gigapedia
Scientific American
Scientific American Mind
科学松鼠会
科幻世界
认识的朋友们
alai
chenyufei
dd
DreamHead
Googol
Jawley
Joyfire
littlestone
lxwde
Matrix67
realfun
RiceBall
roofalison
soloist
Tinyfool
windstorm
YongSun
书剑
云风
余晟
元凯宁
冯大辉(Fenng)
刘新宇
刘江@图灵
史晓明
吴新雨
周星星
周筠@博文视点
孟岩
张志强|阅微堂
张振
徐宥|4G Spaces
方舟@博文视点
曾登高
李笑来|Pure Pleasure
杨军
杨文博
熊节
王信文
王康生
苏杰@阿里巴巴
范怀宇
荣耀
莫华枫
蒋涛
袁泳(g9)|负暄琐话
许式伟
谢东升
谷文栋|Beyond Search
邹欣@MSRA
郑昀
阿朱
陈冀康@华章
陈怀兴
鲍志云
存档
Csdn Blog version 3.1a
Copyright © 刘未鹏