无效的指针、引用和迭代器
首先以演示样例代码为例:
vector<int> v; //加入一些元素 fir(int i=0; i<10; ++i) v.push_back(i); int* my_favorite_element_ptr = &v[3]; cout<<"My favorite element = "<<(*my_favorite_element_ptr)<<endl; cout<<"Its address = "<<my_favorite_element_ptr<<endl; cout<<"Adding more elements.."<<endl; //加入很多其它元素 for(int i=0; i<100; ++i) v.push_back(i*10); cout<<"My favorite element = "<<(*my_favorite_element_ptr)<<endl; cout<<"Its address = "<<&v[3]<<endl;对于以上的代码,会发生什么样的情况?我们创建了一个包括10个元素的vector,并出于某种原因决定保存一个指向索引位置为3的元素的指针。接着,我们向这个vector加入了还有一些元素,并试图复用前面所保存的指针。这段代码会有什么错误吗?
对于以上的代码。它的输出例如以下:
My favorite element = 3 Its address = 0x1001000cc Adding more elements... My favorite element = 3 Its address = 0x10010028c
注意,当我们向这个vector又加入一些元素之后。元素&v[3]的地址发生了变化。问题主要在于当我们向这个vector加入一些新元素时,现有的元素可能会移动到全然不同的位置。
当我们创建一个vector时,它默认分配一定数量的元素(一般是16)。接着。当我们试图加入超出容量的元素时。这个vector就会分配一个新的、更大的数组,把原先的元素从旧位置拷贝到新位置,然后继续加入新元素,直到新的容量也被用完。旧的内存被销毁,能够用于其它用途。
同一时候,我们的指针仍然指向旧的位置,如今已经是被销毁的内存。因此。假设继续使用这个指针会发生什么情况?假设没有人复用这块内存,我们就比較“走运”。不会注意到发生了什么。可是,即使是在这样的最好的情况下,假设我们写入到这个位置(赋值),它将不会改动元素v[3]的值。由于它已经位于别处。
假设我们运气不佳,这块内存已经被其它人用于其它用途,这样的操作的后果可能极为不妙,有可能改动了正好位于这个位置的一个不相关的变量的值,甚至可能有导致程序崩溃的可能。
前面的演示样例代码中所涉及的是指针,假设涉及的是引用。也会发生相同的事情。
比如:我们不是写成:
int* my_favorite_element_ptr = &v[3];
而是写成:
int& my_favorite_element_ptr = &v[3]
其结果是全然同样的。
原因是引用仅仅是“解引用后的指针”。
它知道一个变量的地址。但为了訪问它所指向的内存。并不须要再变量前面加上星号。因此语法虽不同。但结果却是一样的。
最后。当我们使用迭代器时。也会出现同样的结果。比如:
vector<int> v; fir(int i=0; i<10; ++i) v.push_back(i); vector<int>::const_iterator old_begin = v.begin(); cout<<"Adding more elements..."<<endl; for(int i=0; i<100; ++i) v.push_back(i*10); vector<int>::const_iterator new_begin = v.begin(); if(old_begin == new_begin) cout<<"Begin-s are the same."<<endl; else cout<<"Begin-s are DIFFERENT."<<endl; cout<<"My favorite element = "<<(*my_favorite_element_ptr)<<endl; cout<<"Its address = "<<&v[3]<<endl;
它的输出结果例如以下:
Adding more elements... Begin-s are DIFFERENT.
因此,假设我们保存一个指向某个元素(能够是不论什么元素。并不一定是begin()所指向的元素)。它可能会在vector的内容被改动之后失效,因此vector的内部数组以及begin()所产生的相应迭代器可能被移动到其它位置。
因此。在改动vector之前所得到的指向当中某个元素的不论什么指针、引用或迭代器在vector因为添加元素而被改动之后就不应该再使用。实际上,对差点儿全部STL容器以及全部可能改动容器长度的操作(比如,加入或删除元素),情况都是如此。
有些容器,比如hash_set和hash_map,并不正式属于STL,但它们与STL相似。将来非常可能加入到STL中。在涉及这里所讨论的问题时,它们的行为与STL容器同样:在改动容器之后。迭代器就不再有效。
尽管有些STL容器在加入或删除元素之后。仍然保留原先指向它的元素的迭代器,但STL库的总体精神是能够用一种容器替换还有一种容器,而原先的代码仍然没有问题。因此,在STL容器或类似STL的容器被改动之后,就不应该如果它的迭代器仍然是有效的。
注意:在前面的演示样例代码中。我们在訪问这个指针同样的线程内改动了容器,假设一个线程中保存了一个指针、引用和迭代器,同一时候在还有一个线程中改动容器,不仅会出现同样的问题,还会导致更复杂的情况。
有趣的是。在以上的演示样例代码中,索引在指针失败时仍然起作用:假设通过一个基数为0的索引标记了一个元素(即第一个样例中,对int index_of_my_favorite_element = 3这种语句)。这个样例可以正确的继续运行。当然,使用索引的开销(速度更慢)要大于指针。由于訪问与索引相应的元素时,vector必须运行一些运算。即每次使用[]操作符时计算变量的地址。
它的长处是可以起作用,缺点是仅仅适用于vector。对于全部其它STL容器,一旦改动了容器。必须再次找到指向所需元素的迭代器。
总结:改动了容器之后,不要再保存指向容器内元素的指针、引用或迭代器。