刘未鹏|C++的罗浮宫

Knowledge sharing is the best reuse

用户操作
[即时聊天] [发私信] [加为好友]
刘未鹏ID:pongba
957831次访问,排名31好友45人,关注者228
兴趣:人工智能、机器学习、知识发现,认知科学。
pongba的文章
原创 105 篇
翻译 8 篇
转载 0 篇
评论 1817 篇
刘未鹏的公告
除非特别声明,本站采用Creative Commons License许可。转载请保留作者、出处。非商业。

FeedSkyFeedBurner
或者用 鲜果 GR 抓虾 订阅。

CSDN Blog暂时不支持RSS全文输出,对此感到不便的朋友可以使用强大的greasemonkey脚本:GReader Preview Enhanced(链接),该脚本支持在GReader里面直接打开全文页面。

我经常出没于TopLanguage讨论组

我的豆瓣TwitterDelicious

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

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

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

讨论问题请到TopLanguage小组

TopLanguage


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

pongba@gmail.com
pp_liu@msn.com

搜索C++的罗浮宫上的内容(不要回车,点击Go)

twitters

books I've translated




这个Blog上都写了哪些东东

最近评论
kewan001:我也时不时的思考,关于如何学习,记忆等这些问题,但不曾看过什么著作,你带我看到了一个新的世界,谢过
biermando:收藏!
chenxiaoshun:恐怕是我在CSDN所看过的最好的文章。
xuxiandi:佩服啊。。。敬仰中。
YOYOZXR:偶像啊!
文章分类
收藏
相册
其它图片
文章中的图片
我的大头贴
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
不认识的朋友们
fatalerror99
Glacier
realazy
SpiritEpic
TK
Yelz
丁丁虫
冰云
刘慈欣
卢昌海
吴欣安(atppp)
姬十三
林达华
浦宇平
程化
阮一峰
高远
鲍盛
机器学习/数据挖掘/信息检索/自然语言处理/认知科学/人工智能
AAAI
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
其它
Gigapedia
Scientific American
Scientific American Mind
科学松鼠会
科幻世界
认识的朋友们
alai
chenyufei
dd
DreamHead
duguguiyu|Venus神庙
Googol
Joyfire
littlestone
lxwde
Matrix67
realfun
RiceBall@cnBlogs
RiceBalll
roofalison
soloist
Tinyfool
windstorm
YongSun
书剑
云风
余晟|乱象&乱想
冯大辉(Fenng)
刘新宇
刘江@图灵
史晓明
周星星
周筠@博文视点
孟岩
张志强|阅微堂
张振
徐宥|4G Spaces&Web 2.3
方舟@博文视点
曾登高
李笑来|Pure Pleasure
杨文博
熊节
王信文|地球没有好朋友
王康生
荣耀
莫华枫
蒋涛
袁泳(g9)|负暄琐话
许式伟
谢东升
谷文栋|Beyond Search
陈冀康@华章
陈怀兴
鲍志云
存档
订阅我的博客
XML聚合  FeedSky
订阅到鲜果
订阅到Google
订阅到抓虾
订阅到BlogLines
订阅到Yahoo
订阅到GouGou
订阅到飞鸽
订阅到Rojo
订阅到newsgator
订阅到netvibes

原创 《C++0x漫谈》系列之:Auto的故事收藏

新一篇: 《C++0x漫谈》系列之:瘦身前后——兼谈语言进化 | 旧一篇: 玩的岁月

C++0x漫谈》系列之:Auto的故事

 

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)。所以我就不作重复劳动了:),我会尽量从一个宏观的层面,如特性引入的动机,特性引入过程中经历的修改,特性本身的最具代表性的使用场景,特性对编程范式的影响等方面进行介绍。至于细节,大家可以见每篇介绍末尾的延伸阅读。

 

 

Auto

 

上次说到,这次要说的是auto。此auto非彼auto,大家知道C++中原本是有一个auto关键字的,此关键字的作用是声明具有automatic(自动)存储期的局部变量,但跟register关键字一样,它也是个被打入了冷宫的关键字,因为C++里面的(非静态)局部变量本身就是auto的,无需多用一个auto来声明。

 

然而,阴差阳错的,autoC++09中获得了新生。

 

问题

#1

还记得有多少次你对着这样的代码咬牙切齿的?

 

for(std::vector<int>::iterator iter = cont.begin(); iter != cont.end(); ++iter) {

// …

}

 

你根本不关心cont.begin()返回的具体类型是什么,你知道它肯定是一个迭代器。所以你其实想写的是:

 

for(?? iter = cont.begin(); iter != cont.end(); ++iter) {

// …

}

 

??”处填入适当的东西。

 

况且,显式写出std::vector<int>::iterator还有一个坏处,就是当你将cont的类型从vector改为list的时候,这个类型也需要相应改变。简而言之,就是违背了所谓的DRY原则(或TAOUP中所谓的SPOT原则):同一份信息在代码中应该有一个单一的存放点。违反DRY原则被认为是很严重的问题,一份信息如果存放在两处地方,维护的负担就会增加一倍,修改一处便需要同时修改另一处;有人甚至提出代码中重复成分跟代码的糟糕程度是成正比关系的,不无道理。

 

有些书当中会建议你使用typedef来解决上面这个问题:

 

typedef std::vector<int>::iterator iter_t;

 

然后将使用std::vector<int>::iterator的地方全都改用iter_t。这样当你修改cont类型的时候,只需修改typedef一处即可。但typedef的坏处在于,你总归还是要写一个typedef,这个typedef的唯一作用便是为声明iter的地方提供类型,严格来说,这个typedef只是一个蹩脚的workaround。而且,此外这个typedef中仍然还是重复了std::vector<int>这一信息,为了去掉这一信息,又需要引入一个typedef

 

typedef std::vector<int> cont_t;

typedef cont_t::iterator iter_t;

 

cont_t cont;

for(iter_t iter = cont.begin(); … ) {

// …

}

 

显然,这种做法很臃肿,并没有达到KISS标准。

 

另一方面,在许多脚本语言中,变量是没有类型的,我们只要写形如:

 

iter = cont.begin()

 

就行了。

 

很显然,在这个问题上,C++的类型系统给我们带来了麻烦。一门语言应该让我们可以不去关心根本不用关心的东西,将精力放在真正要做的事情上面,在这个例子中我们根本不关心cont.begin()返回的东西的具体类型是什么,我们只关心它能做什么(一个迭代器)。

 

#2

还有一次,我在使用boost.lexical_cast库,我写下:

 

std::string s = boost::lexical_cast<std::string>(i);

 

这里,std::string出现了两次,我明明已经告诉编译器我想把i转换为string了,却还要给s一个string类型——s的类型当然肯定是string了这还用说吗?除了白白磨损键盘之外,如果我后来要把i转换成其它类型的话,便要修改两处地方。

 

同一个项目中,我使用了boost.program_options

 

unsigned long num_labels = vm["num-labels"].as<unsigned long>();

 

这跟上面的代码是同样的问题,unsingned long出现了两次。

 

#3

但所有这些都不是最严重的,因为毕竟你还知道返回类型是什么:你知道cont.begin()返回的是std::vector<int>::iterator,你知道lexical_cast<string>返回的是string,但是你知道:

 

_1 + _2

 

返回的是什么吗?

 

_1_2boost.lambda中的预定义变量,“_1+_2功能是创建一个匿名的二元函数,它的作用是将两个参数相加然后返回相加的结果,相当于:

 

unspecified lambda_f(unspecified _1, unspecified _2) { return _1 + _2; }

 

此处unspecified表示类型不确定,可以是intlong、等任何支持“+”的类型。boost.lambda通过一大堆元编程技巧来实现了这个功能。那么_1 + _2的类型到底是什么呢?

 

lambda_functor<

  lambda_functor_base<

    arithmetic_action<plus_action>,

    tuple<

      lambda_functor<placeholder<1>>,

      lambda_functor<placeholder<2>>

    >

  >

> lambda_f = _1 + _2;

 

int i = 1, j = 2;

cout << lambda_f(i, j);

 

而且,这还只是boost.lambda最简单的表达式。

 

(不完美的)解决方案

对于#1,解决方案可以是std::for_each

 

std::for_each(cont.begin(), cont.end(), op);

 

这就避免了每次声明std::vector<int>::iterator iter之苦,也不用显式iter++了。然而,缺乏语言内建的lambda表达式支持,std::for_each只能说是鸡肋。每次使用的时候都要跑到函数外面定义一个仿函数类(就算这个仿函数的逻辑只有一行,也要人模人样的写一个class定义出来),你说累不累啊?

 

在编码时,信息的局部性是很重要的,好的编码规范建议你在真正使用到一个变量的时候再去声明它,这样一个变量的声明点就紧紧靠在它的使用点上,一目了然(另外一个好处是有可能代码分支根本就执行不到这个变量声明点上,从而省去构造/析构该变量的开销),反之,另一种风格就是把所有(可能用到)的变量一股脑儿全都声明在函数的一开始,这个做法的问题是潜在开销以及可维护性负担。一个长达千行的函数,当我在后面看到某个变量,想看看它是什么类型的时候(变量的类型往往也能提供有用的信息),往上翻了老半天才找到(当然,有IDE的查找支持会好一些,但对象的构造析构开销依然存在)。

 

对于这里的仿函数op来说,对代码阅读者构成的影响是,读代码者必须转到op的类型的定义处(很可能要往上翻页才行)才能看到其逻辑是怎样的。此外,就算有IDE智能提示,op的问题还在于,如果它是state-ful的仿函数(即带有成员数据),就必须在构造函数里面把数据传进去,很麻烦。

 

lambda function(也叫closure)的支持是另一个主题,我们下次讨论)

 

那么有没有更好的办法呢?不用写functor class如何?可以。

 

BOOST_FOREACH(int i, cont) {

// …

}

 

许多语言都内建了foreach,可见其重要性(本来循环就是编码活动中最常见的控制结构之一)。然而,foreach比之经典的for的能力从根本上却削弱了。foreach的循环是隐式的,每重循环我们只能看到这重特定循环访问到的那个数据i。而for循环是显式的,你不仅可以看到i,还可以看到迭代器当前所在的位置,之前之后的位置。比如说,在foreach里面,你不可能“记录下前一个位置”。

 

话说回来,foreach还是很有用的。尤其是当我们的逻辑是“对一个序列中的每个元素挨个做某件事情”的时候,使用foreach能够不多不少不肥不瘦的精确表达我们的意思,正所谓as simple as possible, but not simpler

 

然而,这个方案毕竟只能解决for循环的问题,而且还要面临foreach的限制性。如果我仅仅只是要声明一个iter呢?

 

?? iter = cont.begin();

 

Andrew Koenig早在2002年的时候就在CUJ上发表了一篇文章——“Naming Unknown Types”,描述了对付这一问题的若干种方法:其中之一就是利用typeof,不过typeof毕竟不是语言支持的,只有部分编译器支持,而且typeof的问题在于,容易吸引人违反DRY