从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#include int main() { std::vector v ; v.push_back(10); v.push_back(15); v.push_back(20); std::vector ::iterator i = v.begin(); ++i; std::vector ::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
#include
int main() {
std::vector
v.push_back(10);
v.push_back(15);
v.push_back(20);
std::vector
++i;
std::vector
--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 */ #includeusing namespace std; int main() { string::iterator i1, i2; if (i1 == i2) ; }
情景分析三:把两种不同类型的迭代器用于标准库算法这里所说的不同类型的迭代器并非在容器类型的层面上,实际情况可能比你想象的要糟糕得多,即使同样一种容器的不同实例化实例的容器的迭代器也在于上面所说的不同类型之列,如下例,
程序实例
4:
/* compile with /D_DEBUG /EHsc /MDd */
#include
#include
#include
using namespace std;
// The function object multiplies an element by a Factor
template
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
vector
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
for(vector
{
cout<<":"<<*iter<
while(1)
{}
}
分析:v1和v2都是vector针对int的实例化实例,可是用在for_each算法中也会报iterators incompatible的错误,程序改成下面的形式则不会出错:
程序实例
5:
/* compile with /D_DEBUG /EHsc /MDd */
#include
#include
#include
using namespace std;
// The function object multiplies an element by a Factor
template
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
vector
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
for_each(v1.begin(), v1.end(), MultValue
for(vector
{
cout<<":"<<*iter<
while(1)
{}
}
情景分析四:循环体中的迭代器脱离声明范围i后还引用就会报错
程序实例
6:
// debug_iterator.cpp // compile with: /EHsc /MDd #include#include int main() { std::vector v ; v.push_back(10); v.push_back(15); v.push_back(20); for (std::vector ::iterator i = v.begin() ; i != v.end(); ++i) ; --i; // C2065 }
不过这种情况下报的不是iterators incompatible错误,而是i没有声明,好解决
情景分析五:含有迭代器的类没有运行析构函数(往往是继承中的子类),如果析构函数没有运行,很有可能导致迭代器访问冲突的内存区域,例子如下:
程序实例
7:
/* compile with: /D_DEBUG /EHsc /MDd */ #include
struct derived : base { std::vector
int main() { std::vector
分析:base的虚析构函数我们先把它注释掉,运行结果会有内存冲突,解决的办法是打开base的虚析构函数,从而导致其子类在离开作用域运行自己的析构函数,释放迭代器资源
程序实例8:
/* compile with: /D_DEBUG /EHsc /MDd */ #include
struct derived : base { std::vector
int main() { std::vector
情景分析六:erase删除容器中元素,迭代器失效,这时如果不重新对迭代器赋值,可能会导致iterators incompatibles错误。
程序实例
9:
/*UTF-8*/
/* compile with /D_DEBUG /EHsc /MDd */ #include
int main() { std::vector
for(std::vector 分析:在上例程序中,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 int main() { std::vector for(std::vector return 0; } 总结:伴随着vs系列编译器的变迁的是一系列软件技术的成熟,容器和泛型编程就是其一,虽然初期会弹出很多在过去的编译环境的错误,但是这会有些事实而非的问题扼杀在最初的代码开发阶段,降低了后续工作量,解决一次这样的问题,意味着对容器底层的认识更深一层了。