迭代器失效漫談

 從visual c++2003.net到visual c++2008的編譯器變遷過程中,增加了visual c++runtime library運行庫增加了檢測不正確的迭代器使用情況的assert斷言,運行時一旦發現不正確的迭代器使用,就會彈出 *** iterators incompatible 之類的對話框,錯誤很難定位,因爲沒有提示到底是在程序的那個位置出錯了,只能慢慢縮小範圍一步步定位錯誤。

c++標準描述了一些引起迭代器失效的成員函數,都是修改容器狀態的成員操作,以下是兩個例子:

(1)從一個容器中刪除一個元素,從而引起指向這個元素的迭代器失效

(2)由於插入元素,導致容器的大小發生變化(push,insert),這時候如果還使用原來的迭代器,會造成迭代器失效問題。

情景分析一:在調用了insert或者push方法後還使用原來的迭代器

程序實例:

1,

/* compile with /D_DEBUG /EHsc /MDd */
#include <vector>
#include <iostream>

int main() {
   std::vector<int> v ;
   
   v.push_back(10);
   v.push_back(15);
   v.push_back(20);
   
   std::vector<int>::iterator i = v.begin();
   ++i;
   
   std::vector<int>::iterator j = v.end();
   --j;
   
   std::cout<<*j<<'\n';
   
   v.insert(i,25); 
   
   std::cout<<*j<<'\n'; // Using an old iterator after an insert
}

分析:上面程序中在v.insert(i,25);插入元素後,容器實際上已經發生了變化,所以後面再企圖引用原來的迭代器j就會出問題了!

宏定義_HAS_ITERATOR_DEBUGGING則可以用於關閉迭代器失效運行時檢查斷言,比如下面的程序就不會報錯:

程序實例

2:

// iterator_debugging.cpp
// compile with: /D_DEBUG /EHsc /MDd
#define _HAS_ITERATOR_DEBUGGING 0
#include <vector>
#include <iostream>

int main() {
   std::vector<int> v ;
  
   v.push_back(10);
   v.push_back(15);
   v.push_back(20);
  
   std::vector<int>::iterator i = v.begin();
   ++i;
  
   std::vector<int>::iterator j = v.end();
   --j;
  
   std::cout<<*j<<'\n';
  
   v.insert(i,25);
  
   std::cout<<*j<<'\n'; // Using an old iterator after an insert

   while(1)
   {}
}

但是程序的運行結果卻差強人意:

20
-17891602

明顯,第二個值是迭代器失效的結果!

情景分析二:迭代器沒有初始化

程序實例
3:
/* compile with /EHsc /MDd */
#include <string>
using namespace std;
int main() {
   string::iterator i1, i2;
   if (i1 == i2)
      ;
}

情景分析三:把兩種不同類型的迭代器用於標準庫算法這裏所說的不同類型的迭代器並非在容器類型的層面上,實際情況可能比你想象的要糟糕得多,即使同樣一種容器的不同實例化實例的容器的迭代器也在於上面所說的不同類型之列,如下例,

程序實例

4:

/* compile with /D_DEBUG /EHsc /MDd */
#include <algorithm>
#include <iostream>
#include <vector>
using namespace std;

// The function object multiplies an element by a Factor
template <class Type>
class MultValue
{
private:
    Type Factor;   // The value to multiply by
public:
    // Constructor initializes the value to multiply by
    MultValue(const Type& val ) : Factor(val) { }

    // The function call for the element to be multiplied
    void operator()(Type& elem) const
    {
        elem *= Factor;
    }
};

int main()
{
    vector<int> v1;
    vector<int> v2;

    v1.push_back(10);
    v1.push_back(20);

    v2.push_back(10);
    v2.push_back(20);

 

    // This next line will assert because v1 and v2 are
    // incompatible.
for_each(v1.begin(), v2.begin(), MultValue<int>(-2));

 for(vector<int>::iterator iter=v1.begin();iter!=v1.end();++iter)
 {
  cout<<":"<<*iter<<endl;
 }
 while(1)
 {}
}
分析:v1和v2都是vector針對int的實例化實例,可是用在for_each算法中也會報iterators incompatible的錯誤,程序改成下面的形式則不會出錯:

程序實例

5:

/* compile with /D_DEBUG /EHsc /MDd */
#include <algorithm>
#include <iostream>
#include <vector>
using namespace std;

// The function object multiplies an element by a Factor
template <class Type>
class MultValue
{
private:
    Type Factor;   // The value to multiply by
public:
    // Constructor initializes the value to multiply by
    MultValue(const Type& val ) : Factor(val) { }

    // The function call for the element to be multiplied
    void operator()(Type& elem) const
    {
        elem *= Factor;
    }
};

int main()
{
    vector<int> v1;
    vector<int> v2;

    v1.push_back(10);
    v1.push_back(20);

    v2.push_back(10);
    v2.push_back(20);

 

    // This next line will assert because v1 and v2 are
    // incompatible.
    //for_each(v1.begin(), v2.begin(), MultValue<int>(-2));
 for_each(v1.begin(), v1.end(), MultValue<int>(-2));
 for(vector<int>::iterator iter=v1.begin();iter!=v1.end();++iter)
 {
  cout<<":"<<*iter<<endl;
 }
 while(1)
 {}
}

情景分析四:循環體中的迭代器脫離聲明範圍i後還引用就會報錯

程序實例

6:

// debug_iterator.cpp
// compile with: /EHsc /MDd
#include <vector>
#include <iostream>
int main() {
   std::vector<int> v ;
   
   v.push_back(10);
   v.push_back(15);
   v.push_back(20);
   
   for (std::vector<int>::iterator i = v.begin() ; i != v.end(); ++i)
   ;
   --i;   // C2065
}
不過這種情況下報的不是iterators incompatible錯誤,而是i沒有聲明,好解決
情景分析五:含有迭代器的類沒有運行析構函數(往往是繼承中的子類),如果析構函數沒有運行,很有可能導致迭代器訪問衝突的內存區域,例子如下:

程序實例

7:

/* compile with: /D_DEBUG /EHsc /MDd */ #include <vector> struct base {    // FIX: uncomment the next line    //virtual ~base() {} };

struct derived : base {    std::vector<int>::iterator m_iter;    derived( std::vector<int>::iterator iter ) : m_iter( iter ) {}    ~derived() {} };

int main() {    std::vector<int> vect( 10 );    base * pb = new derived( vect.begin() );    delete pb;  // doesn't call ~derived()    // access violation }

分析:base的虛析構函數我們先把它註釋掉,運行結果會有內存衝突,解決的辦法是打開base的虛析構函數,從而導致其子類在離開作用域運行自己的析構函數,釋放迭代器資源

程序實例
8:

/* compile with: /D_DEBUG /EHsc /MDd */ #include <vector> struct base {    // FIX: uncomment the next line    virtual ~base() {} };

struct derived : base {    std::vector<int>::iterator m_iter;    derived( std::vector<int>::iterator iter ) : m_iter( iter ) {}    ~derived() {} };

int main() {    std::vector<int> vect( 10 );    base * pb = new derived( vect.begin() );    delete pb;  // doesn't call ~derived()    // access violation }

 

情景分析六:erase刪除容器中元素,迭代器失效,這時如果不重新對迭代器賦值,可能會導致iterators incompatibles錯誤。

程序實例

9:

/*UTF-8*/

/* compile with /D_DEBUG /EHsc /MDd */ #include <vector> #include <iostream>

int main() {    std::vector<int> v ;       v.push_back(10);    v.push_back(15);    v.push_back(20);       for(std::vector<int>::iterator iter=v.begin();iter!=v.end();++iter)    {     if(*iter==15)      v.erase(iter);/*錯誤所在*/    }

  for(std::vector<int>::iterator iter=v.begin();iter!=v.end();++iter)    {     std::cout<<"*iter:"<<*iter<<std::endl;    }  return 0; }

分析:在上例程序中,erase刪除元素後,沒有修改iter就繼續循環,在與end()比較時,斷言出現。這裏的主要問題是,vector可以用任意方法實現erase,不保證在erase一個元素後,後續的元素一定被移動到這個iterator所引用的位置(地址)。當然,這在幾乎所有STL的實現中,都是對的,這也就是以前用VC6編譯後運行沒有問題的原因。但如果這裏用的不是vector,而是list或是map,運行到這裏,程序會毫不猶豫的崩潰。正確的做法是這樣的:STL裏所有的容器類的erase實現都會返回一個iterator,這個iterator指向了“當前刪除元素的後繼元素,或是end()”因此,在遍歷容器的所有元素過程中通過erase刪除一個元素後,將erase的返回值賦給迭代變量。

程序實例

10:

/*UTF-8*/

/* compile with /D_DEBUG /EHsc /MDd */ #include <vector> #include <iostream>

int main() {    std::vector<int> v ;       v.push_back(10);    v.push_back(15);    v.push_back(20);       for(std::vector<int>::iterator iter=v.begin();iter!=v.end();)//++iter)    {     if(*iter==15)    {     iter=v.erase(iter);    }     else    {     ++iter;    }    }

  for(std::vector<int>::iterator iter=v.begin();iter!=v.end();++iter)    {     std::cout<<"*iter:"<<*iter<<std::endl;    }

 return 0; }

總結:伴隨着vs系列編譯器的變遷的是一系列軟件技術的成熟,容器和泛型編程就是其一,雖然初期會彈出很多在過去的編譯環境的錯誤,但是這會有些事實而非的問題扼殺在最初的代碼開發階段,降低了後續工作量,解決一次這樣的問題,意味着對容器底層的認識更深一層了。

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章