启用S​​TL迭代器调试真正做了什么?

时间:2010-04-28 16:40:08

标签: c++ stl iterator

我通过定义

在应用程序中启用了迭代器调试
_HAS_ITERATOR_DEBUGGING = 1

我期待这真的只是检查矢量边界,但我感觉它做的远不止于此。实际上正在进行哪些检查等?

顺便说一下,Dinkumware STL。

3 个答案:

答案 0 :(得分:10)

迭代器有许多操作会导致未定义的行为,此触发器的目标是激活运行时检查以防止它发生(使用断言)。

问题

显而易见的操作是使用无效的迭代器,但这种无效性可能由以下各种原因引起:

  • Unitialized iterator
  • 迭代到已删除的元素
  • 迭代到物理位置已更改的元素(重新分配vector
  • [begin, end)
  • 之外的迭代器

标准精确的每个容器的难以忍受的细节,哪个操作使迭代器无效。

有一个不太明显的原因,人们往往会忘记:将迭代器混合到不同的容器:

std::vector<Animal> cats, dogs;

for_each(cats.begin(), dogs.end(), /**/); // obvious bug

这涉及一个更普遍的问题:传递给算法的范围的有效性。

  • [cats.begin(), dogs.end())无效(除非一个是另一个的别名)
  • [cats.end(), cats.begin())无效(除非cats为空?)

解决方案

解决方案在于向迭代器添加信息,以便在执行期间可以断言它们的有效性和它们定义的范围的有效性,从而防止发生未定义的行为。

_HAS_ITERATOR_DEBUGGING符号用作此功能的触发器,因为它不幸地减慢了程序的速度。它在理论上非常简单:每个迭代器都是它发出的容器的Observer,因此被通知修改。

在Dinkumware中,这是通过两个补充来实现的:

  • 每个迭代器都带有指向其相关容器的指针
  • 每个容器都包含它创建的迭代器的链接列表

这巧妙地解决了我们的问题:

  • 单位化迭代器没有父容器,大多数操作(除了赋值和销毁)都会触发断言
  • 已通知擦除或移动元素的迭代器(感谢列表)并知道其无效
  • 在递增和递减迭代器时,它可以检查它是否保持在边界内
  • 检查2个迭代器是否属于同一个容器,就像比较它们的父指针一样简单
  • 检查范围的有效性就像检查我们到达容器末端之前到达范围的末端一样简单(对那些不可随意访问的容器进行线性操作,因此大部分都是这样)

费用

成本很高,但正确性是否有代价?我们可以分解成本:

  • 额外的内存分配(维护了额外的迭代器列表):O(NbIterators)
  • 有关变更操作的通知流程:O(NbIterators)(请注意,push_backinsert不一定会使迭代器失效,但erase会失效
  • 范围有效性检查:O( min(last-first, container.end()-first) )

当然,大多数库算法都是为了最大效率而实现的,通常在算法开始时一次性完成检查,然后运行未经检查的版本。然而速度可能会严重减慢,特别是手写循环:

for (iterator_t it = vec.begin();
     it != vec.end();              // Oups
     ++it)
// body

我们知道 Oups 行很糟糕,但更糟糕的是:在循环的每次运行中,我们创建一个新的迭代器然后销毁它,这意味着为{{分配和取消分配节点1}}的迭代器列表...我是否必须强调在紧密循环中分配/释放内存的成本?

当然,vec不会遇到这样的问题,这是使用STL算法而不是手动编码版本的另一个令人信服的理由。

答案 1 :(得分:0)

据我所知:

_HAS_ITERATOR_DEBUGGING将在运行时显示一个对话框,以断言任何不正确的迭代器使用,包括:

1)删除元素后在容器中使用的迭代器

2)调用.push()或.insert()函数后向量中使用的迭代器

答案 2 :(得分:0)

根据http://msdn.microsoft.com/en-us/library/aa985982%28v=VS.80%29.aspx

  

C ++标准描述了哪些成员函数导致容器的迭代器变为无效。两个例子是:

     
      
  • 从容器中删除元素会导致元素的迭代器变为无效。
  •   
  • 增加向量的大小(推送或插入)会导致向量容器中的迭代器变为无效。
  •