EffectiveSTL3

来源:互联网 发布:码云pages绑定域名 编辑:程序博客网 时间:2024/05/21 18:42

条款13

尽量使用vector 和string 来代替动态分配的数组。

一般认为通过引用计数优化字符串很重要,所以c++ 保证了一个合法的实现。

多线程环境中使用引用计数的字符处串,避免分配和拷贝所省下的时间都花费在后台并发控制上了。

在多线程环境下替代使用引用计数的字符串的三种方案:

第一,看看你的库实现是否可以关闭引用计数,通常是通过改变预处理变量的值。当然那是不可移植的,但使工作变得可能,值得研究。

第二,寻找或开发一个不使用引用计数的string实现(或部分实现)替代品。

第三,考虑使用vector<char>来代替string,vector实现不允许使用引用计数,所以隐藏的多线程性能问题不会出现了。当然,如果你选择了vector<char>,你就放弃了string的专用成员函数,但大部分功能仍然可以通过STL算法得到,所以你从一种语法切换到另一种不会失去很多功能。

条款14

使用reserve来避免比必要的重新分配。

重新分配的负担:内存回收,对象拷贝,析构

只有vector 和string 拥有的所有以下函数:

size()

capacity()

resize(Container::size_type n)  强制把容器改为容纳n 个元素,不论 max_size 大于或小于 n

reserve(container::size_type n) 强制容器把它的容量改为至少n,提供的n不小于当前大小。这一般强迫进行一次重新分配,因为容量需要增加。(如果n小于当前容量,vector忽略它,这个调用什么都不做,string可能把它的容量减少为size()和n中大的数,但string的大小没有改变。在我的经验中,使用reserve来从一个string中修整多余容量一般不如使用“交换技巧”,那是条款17的主题。)

条款15

小心string 实现的多样性

关注string 实现的应用环境,是否需要配置器,内存优化与管理,效率....

  • 字符串值可能是或可能不是引用计数的。默认情况下,很多实现的确是用了引用计数,但它们通常提供了关闭的方法,一般是通过预处理器宏。条款13给了一个你可能要关闭的特殊环境的例子,但你也可能因为其他原因而要那么做。比如,引用计数只对频繁拷贝的字符串有帮助,而有些程序不经常拷贝字符串,所以没有那个开销。
  • string对象的大小可能从1到至少7倍char*指针的大小。
  • 新字符串值的建立可能需要0、1或2次动态分配。
  • string对象可能是或可能不共享字符串的大小和容量信息。
  • string可能是或可能不支持每对象配置器。
  • 不同实现对于最小化字符缓冲区的配置器有不同策略。

条款16

考虑vector 代替数组,string 代替 char*

void doSomething(const int* pInts, size_t numInts);

doSomething(&v[0], v.size());  //唯一的问题就是,如果v是空的。如果这样的话,v.size()是0,而&v[0]试图产生一个指向根本就不存在的东西的指针。这不是件好事。其结果未定义。

// 安全的做法

if (!v.empty()) { doSomething(&v[0], v.size()); }

注意:不要使用v.begin() 代替&v[0]。

因为(这些讨厌的家伙将会告诉你)begin返回指向vector内部的迭代器,而对于vector,其迭代器实际上是指针。那经常是正确的,但正如条款50所说,并不总是如此,你不该依赖于此。begin的返回类型是iterator,而不是一个指针,当你需要一个指向vector内部数据的指针时绝不该使用begin。如果你基于某些原因决定键入v.begin(),就应该键入&*v.begin(),因为这将会产生和&v[0]相同的指针,这样可以让你有更多的打字机会,而且让其他要弄懂你代码得人感觉到更晦涩。

条款17

使用“交换技巧”来修整过剩容量

vector<Contestant>(contestants).swap(contestants);

表达式vector<Contestant>(contestants)建立一个临时vector,它是contestants的一份拷贝:vector的拷贝构造函数做了这个工作。但是,vector的拷贝构造函数只分配拷贝的元素需要的内存,所以这个临时vector没有多余的容量。然后我们让临时vector和contestants交换数据,这时我们完成了,contestants只有临时变量的修整过的容量,而这个临时变量则持有了曾经在contestants中的发胀的容量。在这里(这个语句结尾),临时vector被销毁,因此释放了以前contestants使用的内存。瞧!收缩到合适。

另外,交换技巧的变体可以用于清除容器和减少它的容量到你的实现提供的最小值。你可以简单地和一个默认构造的临时vector或string做个交换:

vector<Contestant> v;string s;...// 使用v和svector<Contestant>().swap(v);// 清除v而且最小化它的容量string().swap(s);// 清除s而且最小化它的容量

 条款18

避免使用vector<bool>

做为一个STL容器,vector<bool>确实只有两个问题。第一,它不是一个STL容器。第二,它并不容纳bool。

一个东西要成为STL容器就必须满足所有在C++标准23.1节中列出的容器必要条件。在这些要求中有这样一条:如果c是一个T类型对象的容器,且c支持operator[],那么以下代码必须能够编译:

T *p = &c[0];// 无论operator[]返回什么,// 都可以用这个地址初始化一个T*

换句话说,如果你使用operator[]来得到Container<T>中的一个T对象,你可以通过取它的地址而获得指向那个对象的指针。(假设T没有倔强地重载一些操作符。)然而如果vector<bool>是一个容器,这段代码必须能够编译:

vector<bool> v;bool *pb = &v[0];// 用vector<bool>::operator[]返回的// 东西的地址初始化一个bool*

但它不能编译。因为vector<bool>是一个伪容器,并不保存真正的bool,而是打包bool以节省空间。在一个典型的实现中,每个保存在“vector”中的“bool”占用一个单独的比特,而一个8比特的字节将容纳8个“bool”。在内部,vector<bool>使用了与位域(bitfield)等价的思想来表示它假装容纳的bool。

vector<bool>不满足STL容器的必要条件,你最好不要使用它;而deque<bool>和bitset是基本能满足你对vector<bool>提供的性能的需要的替代数据结构。

第一个是deque<bool>。deque提供了几乎所有vector所提供的(唯一值得注意的是reserve和capacity),而deque<bool>是一个STL容器,它保存真正的bool值。当然,deque内部内存不是连续的。所以不能传递deque<bool>中的数据给一个希望得到bool数组的C API[1](参见条款16),但你也不能让vector<bool>做这一点,因为没有可移植的取得vector<bool>中数据的方法。

第二个vector<bool>的替代品是bitset。bitset不是一个STL容器,但它是C++标准库的一部分。与STL容器不同,它的大小(元素数量)在编译期固定,因此它不支持插入和删除元素。此外,因为它不是一个STL容器,它也不支持iterator。但就像vector<bool>,它使用一个压缩的表示法,使得它包含的每个值只占用一比特。

 

原创粉丝点击