为何某些公司不允许使用C++ STL?

说几个STL的缺点吧,虽然都是在比较极端的情况下出现,但是对于一些大项目还是会遇到的

1. 代码膨胀问题
每一个实例化过的模板类,都会膨胀出一份独立的代码,比如
std::vector<std::string>, std::vector<int>,编译后会产生两份代码,在VC2008下,每份代码大约是3-4kb,这是因为vector比较简单代码少,如果是map则会产生30-50kb的代码,因为map里有个复杂的红黑树。对于数据处理类的代码里一般会定义很多种不同的结构体,不同的结构体放到不同的容器里,就会实例化出很多个类的代码,我见过一个项目里,这样的vector就有数百个。

2. 内存使用效率问题 (以vc++2008为例)
stl在内存使用效率上是比较低效的,比如std::string,它的sizeof大概是28,因为它有一个内置的16字节数组,用来做小字符串优化的,就是说低于16字节的字符串都会至少占用28字节内存,如果刚好17字节字符串,则会占用28字节+额外分配的字符串内存,额外分配的内存是一个堆块,又有很多浪费,相比用一个char *存储字符串大约多占用了一倍内存。
还有map<>,每一个map的node都是一块独立分配的内存,如果是 map<int, int>呢,那就很悲剧了,为了存一个int要消耗几十个字节,很浪费的。
如果元素数量有百万级,那么内存占用就很可观了,这种情况下建议自己实现allocator,做内存池。

3. deep copy问题
让两个容器的实例做赋值操作,看起来就一条语句,实际上容器里的每个元素都执行了一次赋值操作。如果容器里有百万级的数据,那么一个等号就产生了几百万次的构造和析构。
传递参数的时候一定要用 const 引用,赋值可以用 swap代替。

4. 隐式类型转换
比如 有个函数
void doSomething(const std::string &str);
调用的时候 
doSomething("hello");
能编译执行,但是会产生一个临时的匿名的std::string实例,把"hello"复制一遍,然后在调用完成后析构掉。如果这个发生在循环体内部有可能影响性能。

以上这些问题,在小程序里或者数据规模不大的时候,比如容器内元素只有几千这个规模,都不是什么大问题,那时开发效率才是重点,但是一旦有大数据stl容器会成为性能瓶颈的。

我并不是主张不用STL,而是要充分了解STL的优缺点,根据应用场景做选择。

***********************************************************************************************************************************************************

最初开始禁用 C++ STL,更多地是早期项目编码实践中留下的惯例,被后来的程序员继承下来。老项目中这种选择尤其地多。不过如果有人将其上升到公司行为在不同项目中全面禁用 STL,则没有必要,而且我倾向于做这种决定的人并不理解 C++ 编译系统。

一般来说,项目中禁用 C++ 多见于两种具体场景:或者项目的产出产品为函数库,或者需要引用第三方函数库。具体地来说,有三个主要原因:

第一个原因是二进制边界混乱。对需要在项目中使用第三方函数库的程序员来说,二进制边界是个头痛的问题。C++ 在这一方面本身就处理得不算好,加上模板后起到的是雪上加霜的后果。没有经验的程序员会贪图方便而在公开头文件中使用 C++ 模板,如果这时调用方的编译器选项设置或 STL 版本和编译方不同,那么就可能出现同样的头文件在不同的环境下二进制布局不符的情况。——顺便说一句,在过去十年里,各个主流编译器附带的 STL 版本变化节奏不慢,所以这种由于编译环境不同而导致的 bug 并不算罕见,但缺乏汇编知识的用户难以排查。

第二个原因是不愿使用异常。如今除了 Android 上的 STLPort 关闭异常,大部分主流 C++ STL 实现里都无法脱离异常使用 STL。异常带来的问题主要是两个:性能下降,代码膨胀。这几年 C++ 编译器在性能方面的改进很多,good path 的性能问题已经基本没有,但代码膨胀问题却没有太多改善,甚至这个性能问题的一部分解决方案就是以代码膨胀为代价。我写过一篇短文比对过 Android 上 gcc 4.6 在有无异常的情况下的汇编代码逻辑,可以看到,启动异常时生成的汇编代码量多出了相当一部分(我的例子中是 50%),用于处理各种隐含代码中的异常问题。这一条在手机系统中有时候会引起意想不到的麻烦,比如软件升级后导致 app 在低存储容量的手机中安装失败。顺便说一句,这个问题并不是 gcc 独有,clang 上生成的代码是一样的。参考:http://dummydigit.net/posts/2014-01-01-23-30-1.html

最后一个原因是 C 兼容。严格地说,STL 在这个问题上算是躺枪,这个坑在很多具体的场景中也是因为异常而引入,但这个问题的麻烦程度比前两个问题更高。比如 gcc 在编译纯 C 代码时默认关闭 -fexceptions 选项,因此这样编译出来的代码中没有异常处理相关的栈展开。如果某个 C++ 项目引用了一个第三方 C 项目,它很难确保那个 C 项目给出的二进制代码中正确进行了异常处理并保证代码服从异常安全操作。这种场景下混用 C/C++ ,就可能在抛出异常时莫名其妙地崩溃或者出现 C 代码区段中的资源泄漏,特别是 expat 那种大量利用回调的代码结构。要规避这种风险并非不可能,但需要 C 的架构部分做修改,比如使用 DOM 那种树形结构,这种做法对历史项目而言又很难办到。换言之,如果一个项目出于种种原因需要保持 C 兼容,而 STL 就属于其中一个不可控的变数,与其相信程序员不犯错,不如直接禁用更可控一些。参考:Code Gen Options

要解决二进制相关的问题很简单:整个项目的所有相关代码在同一个代码基上编译,强制打开编译选项添加异常代码,并去除一切二进制依赖。但对很多小公司来说,引入这样的系统对配置管理的要求较高。如果一部分依赖关系来自自己并不了解的第三方代码,轻易修改编译选项可能带来的风险与第三方代码库的规模成正比。退一步说,即便团队里真的有强大的配置管理工程师能够搞定一切,他们也不会有能力解决代码膨胀问题,除非他们有权决定换一个编译器。相比之下,前面朋友所说的所谓性能或者编译出错时糟糕的可读性,在我看来反倒是次要因素,而且这些缺陷都正在新的编译器中逐步得到解决或改善,比如 clang。

所以那些选择禁用 STL 的早期项目负责人,总有一些确实的理由,没有人那么别扭地想跟开发效率过不去。至于后来的人是真的仔细想过这些细节还是人云亦云,那是另一个问题。

****************************************************************************************************************************************************************

一些普遍解决方案和回答:

最安全的方式是不要跨 DLL boundary 使用 C++ 对象,尤其是 STL。

DLL 和调用 DLL 程序使用的是不是一个堆,取决于整个工程的 CRT 使用方式。如果 CRT 是动态链接的,那么程序本身和所有 DLL 都使用一个堆。如果 CRT 是静态链接,那么必然是逐一静态链接到程序本身和每个 DLL 上,那么每个模块都是一个单独的堆。

c++ - How can I use Standard Library (STL) classes in my dll interface or ABI?
一个原则是,你的DLL公共类的ABI必须是确定的,除非你自己改代码,否则这个ABI不应该随着DLL的编译器版本而改变,但是STL做不到这一点,因为他的实现不受DLL作者控制。但是STL可以在DLL私有部分使用,只要其细节不被暴露出来即可。

推荐文章:"How to export an instantiation of a Standard Template Library (STL) class and a class that contains a data member that is an STL object“ - 微软

可以,要注意一些细节,先占坑

已填坑

爪机码字一大段直接没了,我的心好痛

1.exe和dll全部使用动态链接
因为dll和exe是链接到同一个运行库dll上的,因此使用的是同一份运行库的代码,内存分配是在一个堆上的。对于所有类所有特性都能跨二进制文件使用

2.只要任意一个使用了静态链接,你就不能向上面一样使用
原因:静态链接会在生成的二进制文件里包含库的代码,那么你的dll和exe使用的其实是两个运行库的代码,自然会有两个不同堆的问题
解决方案:
1.对于dll中导出的对象避开所
有malloc(new)和free(delete)操作,dll中使用exe里的对象也一样
2.将exe里和dll里的malloc(new)和free(delete)地址弄出来,人工避免混用
3.利用c++虚函数。由于虚函数的地址是在运行期绑定的,因此你可以利用这个特性来避免混用exe和dll中的malloc(new)和free(delete)。比如你在dll里new了一个class a并在exe里使用它,那你就不能在exe里直接delete之。你应该为a添加一个virtual的release
virtual void release()
{
delete this;
}
需要释放资源的时候调用release。对于所有需要申请内存的操作同理。

然而对于string在静态链接的情况下除了解决方法1都没有什么卵用,除非你自己实现一个。

references:

http://www.zhihu.com/question/20201972

http://www.zhihu.com/question/32127579

上一篇:欧拉工程第64题:Odd period square roots


下一篇:Python学习之路基础篇--01Python的基本常识