迭代器失效漫谈

 从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系列编译器的变迁的是一系列软件技术的成熟,容器和泛型编程就是其一,虽然初期会弹出很多在过去的编译环境的错误,但是这会有些事实而非的问题扼杀在最初的代码开发阶段,降低了后续工作量,解决一次这样的问题,意味着对容器底层的认识更深一层了。

 

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值