STL vector容器需要警惕的一些坑
从迭代器中取值切记需要判断是否为空
例如:
1 vector<int> vtTest; 2 vtTest.clear(); 3 if (vtTest.empty()){ 4 return 0; 5 } 6 7 int *pTest = &vtTest[0];
如果没有忘了判断则会出现这样的异常_DEBUG_ERROR("vector subscript out of range");
其实这条异常是Visual C++专有,在g++当中并不会出现,所取得的数值是0。可问题是你怎么区分里面所存的数据本身是0还是vector为空呢?
带有容器的结构体不要使用memset清0
例如以下代码:
1 struct _Test{ 2 int i; 3 vector<int> vtTest; 4 }; 5 6 _Test test; 7 memset(&test,0,sizeof(test)); 8 test.vtTest.push_back(0); 9 test.vtTest.push_back(0); 10 test.vtTest.push_back(0);
其实这部分代码完全可以正常运行,但是如果加上以下代码就一样了
1 vector<int>::iterator it = test.vtTest.begin(); 2 it++;
问题就出在it++这条语句之上,此时会抛出vector iterator not incrementable
因为在自加的操作中,有这么一条判断
1 if (this->_Getcont() == 0 2 || this->_Ptr == 0 3 || ((_Myvec *)this->_Getcont())->_Mylast <= this->_Ptr) 4 { // report error 5 _DEBUG_ERROR("vector iterator not incrementable"); 6 _SCL_SECURE_OUT_OF_RANGE; 7 }
在this->_Getcont()函数内部,其实现是这样的;
return (_Myproxy == 0 ? 0 : _Myproxy->_Mycont);
一旦调用上面的memset(&test,0,sizeof(test));之后,vector的_Myproxy数据结构也被清0了,此时_Getcont()函数的返回值就是0,那么程序就会执行到_DEBUG_ERROR("vector iterator not incrementable");经过分析发现,_Myproxy变量是vector用来寻找相邻的数值,而我们的清0操作导致这个链条断裂了,破坏了vector的数据结构从而导致异常。但是g++编译器并没有以上问题,因为VS改写了STL代码。
其实memset清0操作对于结构体或者类都必须慎重,因为很容易破坏自身的数据结构,最典型的就是带有虚函数的类,一旦清0连虚表都给破坏掉了。
vector内存控制
vector每次调用push_back的时候,如果之前内存够用就直接插入,如果不够用了就重新申请一块更大的内存(g++是在原基础上增加一倍长度,VS是增加百分之五十),然后将数据拷贝到新内存当中,释放原内存,再插入新数据。
那么有两个问题,第一,如果能预测到数据量是固定数字,一定要首先预备一块内存,然后插入数据,这样第一避免多次重复申请释放,拷贝等耗资源和时间的无用操作,也可以避免多出一块并不会使用的内存。
其次如果从vector中删除元素,由于vector内存只增不减,当你申请一万个元素空间,删除了9999个,但是其占用仍然是10000个元素空间,如果条件允许,其实可以考虑增加策略来避免内存浪费,因为这些内存只有在析构的时候才会彻底释放掉。如果是指针则必须要手动析构,先遍历逐一delete,然后clear。
由于vector是申请的一块内存,所以如果要从首部删除元素会导致后面的所有元素向前移动一个单位,如果一直这么操作其占用可想而知。。。
每一个实例化过的模板类,都会膨胀出一份独立的代码,比如std::vector<std::string>, std::vector<int>,编译后会产生两份代码,在VC2008下,每份代码大约是3-4kb,这是因为vector比较简单代码少,如果是map则会产生30-50kb的代码,因为map里有个复杂的红黑树。对于数据处理类的代码里一般会定义很多种不同的结构体,不同的结构体放到不同的容器里,就会实例化出很多个类的代码,我见过一个项目里,这样的vector就有数百个。
另外STL在内存使用效率上是比较低效的,比如std::string,它的sizeof大概是28,因为它有一个内置的16字节数组,用来做小字符串优化的,就是说低于16字节的字符串都会至少占用28字节内存,如果刚好17字节字符串,则会占用28字节+额外分配的字符串内存,额外分配的内存是一个堆块,又有很多浪费,相比用一个char *存储字符串大约多占用了一倍内存。还有map<>,每一个map的node都是一块独立分配的内存,如果是 map<int, int>呢,那就很悲剧了,为了存一个int要消耗几十个字节,很浪费的。如果元素数量有百万级,那么内存占用就很可观了,这种情况下建议自己实现allocator,做内存池。
如果让两个容器的实例做赋值操作,看起来就一条语句,实际上容器里的每个元素都执行了一次赋值操作。如果容器里有百万级的数据,那么一个等号就产生了几百万次的构造和析构。传递参数的时候一定要用 const 引用,赋值可以用 swap代替。