库是语言的重要组成部分。对于任何一门语言和开发平台的学习和掌握,都离不开对库的熟练运用。我们说,C语言有CRT和Posix API,Java有J2SE/J2ME/J2EE,C#有.NET Framework,Python也有自己的Library。
对于大多数语言而言,对于语言附带的标准库的使用,简直是天经地义的事情。但是就是这样一个人所尽知的问题,在C++却出现了严重的分化。不要说作为准标准的Boost,就连标准的STL,都会被无数人以“低效”为由拒绝使用。
之前在一个帖子上,和别人争论这个问题争论了很长时间,当然,最终一定是不了了之的。C++标准库的使用居然变成了一种信仰和哲学问题,这也算是计算机语言当中的一朵奇葩了。
这里我将这个帖子当中比较有价值和代表性的留言发出来,也是在这里与大家交流一下。
——————————————————————————————————————————
w:
(做一个游戏引擎)很犹豫,是自己写还是用stl和boost?
g:
都是自己写,不用其它的。
qj:
不需要通过DLL导出的情况下,我用STL用的还是蛮多的。
但是一般不使用boost,boost虽然优秀,但是它的大部分内容都有些“高手的玩具”,或者“时尚人士炫耀的工具”,或者“为了什么什么而牵强附会” 这样的感觉,剩下那些实用的,要么进了标准库,要么有替代品,要么不用也罢,所以这种情况下,我就不想给自己的项目添加那么大的一个依赖项了……
gmm:
我的引擎用,而且是大量使用。大部分人都是因为不了解而产生恐惧,继而不用的。
qj:
一般来说STL还是可以用用的。但是如果你是写对性能很敏感的核心代码的话,还是不要用STL,STL会做一些无谓的bounds check而影响性能,但最主要的是内存分配,很多时候需要使用特殊的内存分配器,但是STL的内存分配器设计是非常失败的,容器的内存分配器居然是跟类 型绑定的,实在是太扯淡了。
至于Boost就没必要去用了,有些库属于语法糖,例如lambda,functional,smart ptr之类,只是写法上更简洁一些,但代价是增加了编译时间,而且受制于C++本身的限制,有些写法其实还是比较别扭的,还不如直接用其他更高级的语言。 Boost试图把很多FPL特性引入到C++里,根本就是画虎不成反类犬。当然有些库还是干了些实事的,比如Asio之类,不过在我看来这些库的整体设计 其实都是比较糟糕的,估计作者们都把心思花在如何堆积技巧上了,而把如何设计好的程序架构忘在脑后了。
3222:
说的好,顶了,我做自己的引擎深有体会!STL 因为是公用库,是要考虑很多情况的,自己引擎的stl,只是针对自己的,速度和性能快很多,而且还有助于管理自己分配内存
sssa:
有的stl的实现确实会有越界的检查例如 stl port。不过我记得是不是只有在debug下才会这样?
内存适配器确实有必要自己实现一个。
我倒不认为stl本身有什么错,如果发现瓶颈在stl上 我觉得很可能是没有使用正确或者从设计上有失误,我不认为能通过自己写一个容器就能大幅度提高性能。
可能出现的情况是 我只需要一个容器的某个很简单的功能但是stl提供了比所需要多的功能,而我不得不为这多出来的功能买单,但是即使着这种情况 我认为也能够从stl其他的用法中得到改善。
一笑:
我们全是自己写的,当然加了宏,可以方便的转成STL.实验发现,用自己的编译出来的EXE比用STL的小了20多KB.我们在自己的模板库里加入了大量 assert,比如数组[]操作符.还有string的c_str().......map则改用哈希实现.同时也自己做了内存管理,我们的vector 最大的不同是,它不是以倍数增大的,而是以实际分配内存的大小.比如我需要4字节,内存管理器实际分配了16字节,vector的容量也增大到16 了.......目前来看效果很不错.
mxh:
stl/boost是多少人严格测试发布的库,怎么会比自己拍脑袋写出来的robust差?
我一般看到用stl/boost导致的性能问题,都是设计不好或者不会用。
ldd:
有点。vector倍数增长是实际证明的最高效增长方式,如果你需要节省内存,它可以compact到实际内存使用量。另外hash_map和 unordered_map现在的STL基本都有。
gmm:
总结一下:
"我就不想给自己的项目添加那么大的一个依赖项了"
boost是由很多基本独立的小库组成的,又不是用一个全都得放进去,我不知道大在哪里了。
"STL会做一些无谓的bounds check而影响性能"
STL不会,VC的STL实现会,而且可以关掉,尤其是VC10的,在release下不会检查。
"但最主要的是内存分配,很多时候需要使用特殊的内存分配器,但是STL的内存分配器设计是非常失败的,容器的内存分配器居然是跟类型绑定的,实在是太扯 淡了。"
stlport的方法就是2层的allocator,一层是类型无关的,上面一层是类型相关的。标准规定了类型相关,不等于就不能有个类型无关的阿。只要 接口和allocator一样,自己写个类型无关的完全可以。
"有些库属于语法糖,例如lambda,functional,smart ptr之类"
functional,smart ptr是语法糖?
"我们在自己的模板库里加入了大量assert,比如数组[]操作符"
VC8+的STL也有,而且有宏可以控制是否执行检查
"map则改用哈希实现"
map有map的用处,hash有hash的用处,不是替换的关系
"同时也自己做了内存管理,我们的vector最大的不同是,它不是以倍数增大的"
首先vector不一定是倍数增长的,也有别的增长方式。第二预留一定的空间可以让new/delete次数降低,否则人家没必要这么做。
qj:
"STL会做一些无谓的bounds check而影响性能"
STL不会,VC的STL实现会,而且可以关掉,尤其是VC10的,在release下不会检查。
bounds check是跟STL实现相关的,在VC里当然也是可以关掉的。我以前一直以为STL在Release下不会做bounds check,直到有一次反汇编了一下代码,再查看了一下源代码才发现STL里有bounds check。当然这个不是什么大问题。
"但最主要的是内存分配,很多时候需要使用特殊的内存分配器,但是STL的内存分配器设计是非常失败的,容器的内存分配器居然是跟类型绑定的,实在是太扯 淡了。"
stlport的方法就是2层的allocator,一层是类型无关的,上面一层是类型相关的。标准规定了类型相关,不等于就不能有个类型无关的阿。只要 接口和allocator一样,自己写个类型无关的完全可以。
2层allocator也帮不上忙,其实我需要的是给某个容器实例指定一个allocator。比如我用某个list<int>容器,我希望 每个元素用我指定的allocator的来分配,这个allocator可以直接在栈上开辟一个临时空间,等函数返回的时候一次性释放掉,而不需要再一个 个的释放。像这样:
allocator alloc = allocator(_alloca(10000), 10000);
list<int> l = list<int>(alloc);
"有些库属于语法糖,例如lambda,functional,smart ptr之类"
functional,smart ptr是语法糖?
没有smart ptr而用原生指针不会影响程序功能,只不过多写几行释放代码而已。functional也是一样道理。这就是语法糖。
我:
去看看container的constructor吧。。。
还有,smart_ptr是语法糖。。。呃,我彻底无语了。
gmm:
"bounds check是跟STL实现相关的,在VC里当然也是可以关掉的。我以前一直以为STL在Release下不会做bounds check,直到有一次反汇编了一下代码,再查看了一下源代码才发现STL里有bounds check。当然这个不是什么大问题。"
你是特指VC9吧。VC8和9我都会在release里面定义_SCL_SECURE=0把check关掉。VC10默认就是release没check 的。
"我希望每个元素用我指定的allocator的来分配"
这当然可以了,从来都可以,本来就可以。
"没有smart ptr而用原生指针不会影响程序功能,只不过多写几行释放代码而已"
关于语法糖是什么意思,相信你还得看看。你对这三个字的理解有误。引用计数都称语法糖的话,还有什么不算?
我:
不是给语言增加新功能。而是库级别没有办法实现,又不改变语言基本假设、主要方法论和开发范式的语法形式和语法元素,一般都称之为语法糖。 smart_ptr并没有对语法做出任何改善,而是侧重在引用计数的功能关注点上,因此属于库的级别,不能称作是语法糖。
qj:
补充几点:
1、list是可以在构造函数里指定分配器,但是类型定义里必须带上分配器的类型,这就是我之前说STL的容器类型是和分配器绑定的。
2、smart_ptr跟引用计数是两码事。COM也是引用计数的,但没有规定COM就要用smart_ptr访问。反之也没规定smart_ptr必须 是引用计数的。
3、说这些东西是语法糖,因为这些东西在其他语言里就是语法糖,他们不提供功能而只是使写法更简洁一些,Boost用库的形式提供了这些语法糖。没有 Boost实现的这些糖,单纯使用C也可以做到这些功能,只是写法上麻烦一些而已。如果你认为用库实现的东西不叫语法糖,那我也没意见,但是本质上他们是 一类东西。
xj:
要是stl/boost能像.net framework那样统一就好了... boost用多了感觉代码风格都变了, 完全像在使用另一门语言的样子
我:
stl和boost大体上是统一的。
实际上STL和Boost针对Developer和User哲学是不一样的。
对于Boost开发者而言,强调的是代码可读,高效,强调元编程和编程技巧。
对于User而言,boost和STL分为四种风格。
第一种风格为Lib风格,以提供功能为主。例如Pool,Graph等,也包括楼上一直在争论的Smart_Pointer和Asio。这一类风格的用 法,是典型的双阶段的,第一阶段是型别特化,第二阶段是基于编译器/运行时接口的引用。STL和BOOST里,大部分库都是这样的风格。这也是最容易使用 和使用频率最高的风格。
第二种风格是语法糖类。for each等都属于这一类。这一类库,通常是按照As-is的方式使用的。
第三种风格是范式和方法论的拓展,即在C++中模拟其他编程范式和方法论。例如spirit,lambda,proto。严格的说,boost.mpl也 可以归属此类。这一类库的使用方式分为两步,第一步是定制方言,第二步是使用方言。
第四类风格,是元编程。利用模板和宏进行编译器推导,以实现代码展开、选择编译等工作。典型的例子有 boost.pp,stl/boost.type_traits,enable_if等,这一部分对于一般用户是可以不用的。
所谓的boost和stl风格不统一,大致上是因为stl仅包含第一种,而boost包含了全部四类的库的风格。
实际上单就lib形式使用的库而言,boost和stl风格几乎是完全一致的。而且,所谓的编译时间过长的问题,在lib一系的库中,也基本上不存在。所 以所谓boost难用,只是眉毛胡子一把抓,不会对库进行分类的问题。并且,不管哪一类,boost都是强调接口对用户友好。只是不同层次上的库,友好的 方面和友好程度是不同的而已。
至于说.net的形式统一,你觉得.net的reflection能够和xml一类的在使用感觉上是一致的么?很显然不可能。只不过C++的多范式设计, 加剧了这个问题而已。
qj:
看楼上说的头头是道,我也懒得再说什么了。
我只想说的是,Boost基本的设计思路就是不正确的,即想追求简洁性又要求通用性还要保持性能没有损失,这么多目标合在一起造就了boost这样一头四 不像的怪兽。正如linus对C++的批判所言,真正重要的是设计。一般来说,大部分的设计目标都不是正交的,比如为了性能就需要牺牲一些通用性,为了可 靠性就要降低一些系统的复杂性,所以设计的重点在于取舍,根据设计目标在通用性,性能,系统复杂度等多个因素上做出正确的取舍。boost的很多库在设计 时为了同时满足多种设计目标,所付出的代价是大大提升了系统的复杂性,系统因此变得臃肿不堪,并且又企图用编程技巧来掩盖设计上的缺陷,产生出来的只是一 堆糟糕的设计。而且Boost的这种技巧凌驾于设计的风格,毒害了很多经验不足的C++程序员,让他们沉醉于构造那些花哨的模板技巧,而忘记了编程的本来 目的。
我:
我也确实不知道楼上还有什么要说的。
很显然,技巧是高手们使用的。所谓“被毒害”,只不过是因为模仿的火候不到,不能怪语言设计者和库作者。我前面也说过了,你用Lambda和Spirit 的复杂度去讨论Smart Ptr很显然是一种愚蠢的不合时宜的行为。难道你觉得Smart Pointer,Pool的设计是臃肿的么?难道你觉得any的设计是多余的么?难道boost提供的,TR1的Unordered Map的实现,是“拙劣的”么?那么,sorry,即便是C函数库,也不可能尽满足你的要求。
库在设计时的考量本身就是一种权衡,不管什么库都是。即便是CRT这样的库。Malloc一样有诸多问题,因而才会有TCMalloc这样的第三方库。如 果你喜欢,你大可以用汇编去整一套你想要的东西出来。
可是你觉得这样现实么?
Poc:
娃哈哈,天朝技术人员的老毛病了——靠企图打到别人来证明自己的高明。
没错,任何东西都要辩证的看,但光辩证不唯物那就是诡辩了——说白了和泼妇吵架没啥区别。
其实我是C控,希望的设计是用C作为接口的若干松耦合模块作为底层,然后使用脚本语言或者带gc的高级语言做上层逻辑。我是提倡用C的方式使用C++,但 并不是排斥C++的特性,当然某个东西用还是不用是得在充分了解之后了,在这之前更不敢妄加评论了。
qj:
Рос同学不必妄自菲薄,技术之争在哪里都有,国外的论坛上关于某项技术或者语言的争论也是喋喋不休的,争论只是形式不是目的,通过争论加深对某些 东西的理解,同时可以听到一些不同的声音,这才是我们应该从争论中得到的收获。
关于boost的smart ptr我不认为他的设计是好的设计,boost的smart ptr的设计是个野心勃勃的设计,试图提供一揽子的解决方案把所有可能的smart ptr特性都提供给用户,比早年Loki中的smart ptr做的还要多。所带来的问题是严重增加了使用者的学习成本,使用者需要考虑的因素太多,很容易用错。比方说把单线程的share_ptr用在多线程环 境里。smart ptr在我的工程里也经常会用到,我通常会自己实现一个简单的smart ptr,只为我需要的特性来设计,不到100行代码就可以搞定。
gmm:
"比方说把单线程的share_ptr用在多线程环境里"
shared_ptr是atomic的阿,不分单和多。
——————————————————————————————————————————————————
把这段话放在这里的目的,不是说谁对或者谁错,而是说阐明一下不同的人对STL和Boost的不同理解。当然,这里面,有些是值得商榷的,而另外有一些,特别是qj的话中,是有不少错误存在的。一些错误我和gmm在回复中给予了纠正,另外一部分错误,可能就需要大家对模板和STL的代码都有比较深入的了解才能发现了。
我的观点很明确,对于BOOST和STL,国内的C++社区存在着太深的误解和偏见。特别是Boost,由于这个库设计技巧极为复杂,然而用户接口却又非常干练易用。很多人便将Boost库的使用难度和设计难度等同到一起,进而产生畏难情绪。
我始终坚持对于大部分人而言,学会使用STL和Boost的意义是不言而喻的,而且,它比学会STL和Boost本身的设计构建技巧重要的多。