迭代器失效漫談

來源:互聯網
上載者:User

 從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系列編譯器的變遷的是一系列軟體技術的成熟,容器和泛型程式設計就是其一,雖然初期會彈出很多在過去的編譯環境的錯誤,但是這會有些事實而非的問題扼殺在最初的代碼開發階段,降低了後續工作量,解決一次這樣的問題,意味著對容器底層的認識更深一層了。

 

 

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.