条款13:尽量使用vector和string来代替动态分配的数组
条款13:尽量使用vector和string来代替动态分配的数组
这一刻,你决定使用new来进行动态分配,你需要肩负下列职责:
- 你必须确保有的人以后会delete这个分配。如果后面没有delete,你的new就会产生一个资源泄漏。
- 你必须确保使用了delete的正确形式。对于分配一个单独的对象,必须使用“delete”。对于分配一个数组,必须使用“delete []”。如果使用了delete的错误形式,结果会未定义。在一些平台上,程序在运行期会当掉。另一方面,它会默默地走向错误,有时候会造成资源泄漏,一些内存也随之而去。
- 你必须确保只delete一次。如果一个分配被删除了不止一次,结果也会未定义。
职责真多,而且我不能理解为什么如果可以省心你却还要负责。感谢vector和string,用了它们就可以不像以前那么麻烦了。
无论何时,你发现你自己准备动态分配一个数组(也就是,企图写“new T[...]”),你应该首先考虑使用一个vector或一个string。(一般来说,当T是一个字符类型的时候使用string,否则使用vector,但我们在本条款的后面将遇到的情况中,vector<char>可能是一个合理的设计选择。)vector和string消除了上面的负担,因为它们管理自己的内存。当元素添加到那些容器中时它们的内存会增长,而且当一个vector或string销毁时,它的析构函数会自动销毁容器中的元素,回收存放那些元素的内存。
另外,vector和string是羽翼丰满的序列容器,所以它们让你支配可以作用于这样的容器的整个STL算法军火库。虽然数组也可以用于STL算法,但没有提供像begin、end和size这样的成员函数,也没有内嵌像iterator、reverse_iterator或value_type那样的typedef。而且char*指针当然不能和提供了专用成员函数的string竞争。STL用的越多,越会歧视内建的数组。
如果你关心你必须继续支持的遗留代码,它们都是基于数组的,放松点,无论如何都应该使用vector和string。条款16演示了把vector和string中的数据传给需要array的API有多简单,所以整合遗留代码一般都没有问题。
坦白地说,我想到了一个(也是唯一一个)用vector或string代替动态分配数组会出现的问题,而且它只关系到string。很多string实现在后台使用了引用计数(参见条款15),一个消除了不必要的内存分配和字符拷贝的策略,而且在很多应用中可以提高性能。事实上,一般认为通过引用计数优化字符串很重要,所以C++标准委员会特别设法保证了那是一个合法的实现。
唉,一个程序员的优化就是其他人的抱怨,而且如果你在多线程环境中使用了引用计数的字符串,你可能发现避免分配和拷贝所节省下的时间都花费在后台并发控制上了。(细节请参考Sutter的文章《Optimizations That Aren't (In a Multithreaded World)》[20]。)如果你在多线程环境中使用引用计数字符串,就应该注意线程安全性支持所带来的的性能下降问题。
要知道你正在使用的string实现是否是引用计数的,通常最简单的方式是参考库的文档。因为通常认为引用计数是一种优化,制作商一般把它作为一个特性来吹捧。另一种方法是看库的string实现的源代码。我一般不推荐尝试从库源代码中得到东西,但有时候这是唯一能找出你想知道的东西的方法。如果你选择了这个方法,就要记住string是一个basic_string<char>的typedef(而wstring是basic_string<wchar_t>的typedef),所以你真正需要看的是basic_string模板。最容易检查的地方是可能的类构造函数。看看它是否在某处增加了引用计数。如果是,string就是引用计数的。如果不是,要么就是string不是引用计数,要么就是你看错了代码。呵呵。
如果你用到的string实现是引用计数的,而你想在已经确定string的引用计数支持是一个性能问题的多线程环境中运行,你至少有三个合理的选择,而且没有一个放弃了STL。第一,看看你的库实现是否可以关闭引用计数,通常是通过改变预处理变量的值。当然那是不可移植的,但使工作变得可能,值得研究。第二,寻找或开发一个不使用引用计数的string实现(或部分实现)替代品。第三,考虑使用vector<char>来代替string,vector实现不允许使用引用计数,所以隐藏的多线程性能问题不会出现了。当然,如果你选择了vector<char>,你就放弃了string的专用成员函数,但大部分功能仍然可以通过STL算法得到,所以你从一种语法切换到另一种不会失去很多功能。
所有的结果都是简单的。如果你在使用动态分配数组,你可能比需要的做更多的工作。要减轻你的负担,就使用vector或string来代替。