2011-05-18 54 views
3

STL容器存在迭代器在容器更改时可能失效的问题。容器是否可以通过添加一个调用has_changed()来通知它已经改变了?关于使迭代器无效

在某些操作之前查询empty()很常见。如果容器对会影响迭代器的操作(例如insert()或erase())设置布尔值,那么在重用迭代器之前可以查询has_changed()。

疯了吗?

编辑感谢您的一堆好回复和思考。我希望我能授予不止一个获胜者。

+0

那么,如果你有一个Container :: has_changed()方法,你会怎么做?你会对你的迭代器做什么? – quamrana 2011-05-18 16:33:43

+0

实际上你可能想要一个'bool Container :: is_still_valid(Container :: iterator)'。这解决了“自改变以来?”和“并非所有的迭代器都被每个变更无效”的异议。 (我把它称为'is_still_valid',因为如果你将它限制为过去对那个容器有效的迭代器,实现就会容易得多) – MSalters 2011-05-19 11:05:49

回答

3

在Java中工作的(近似的)快速失败迭代器的工作原理是容器在每次更改时都增加一个计数器。迭代器在创建时复制此计数器,并在每次通过容器更改容器时增加它。如果迭代器检测到不匹配,则会抛出异常。

C++有令人兴奋的属性,一些操作使容器上的迭代器无效,但其他操作无效。例如,假设已经保留足够的空间vector::insert使迭代器在插入点之后失效,但不在之前。

另一个难题是list::remove。这将使所有迭代器都有效,除了被删除的迭代器,和它的所有副本

正如你可以想象的那样,要精确跟踪这一点非常困难。实践中发生的事情是,您的实现可能会提供调试选项,其中迭代器将尽最大努力检测它们是否有效。但是,这可能取决于他们目前是否“有效”的实施细节,而不是标准是否保证他们仍然有效。

在C++中执行类似于Java中的“版本号”的操作是可能的,但它会给出错误的迭代器错误,它们看起来无效但实际上是有效的。

5

有点疯了,如果有意的话。

我认为这个问题的主要原因是容器如何知道它何时“不变”?换句话说,一些东西被抹去,并且“已更改”标志被设置。由于重新恢复正常或稳定状态,重置旗子的事件是什么?它实际上是迭代器而不是处于无效状态的容器。

我认为为了在所有迭代器中工作,需要比现在更多,更智能,并且更像操作容器的观察者。容器可以发送一个事件给它已经改变的注册迭代器,并且他们可以在尝试操作之前检查它们自己的状态。但即使有这么多漏洞,它可能会导致比你想要解决的更大的混乱。

+2

同意答案,一些实现确实增加了对变化检测的支持,特别是Dinkumware的实现随VS一起发货可以用检查迭代器进行编译,如果容器发生了变化,这些迭代器将检测(并中止)。我相信实际的实施是在channel9视频中解释的。请注意,选中的迭代器比未选中的迭代器慢得多。性能影响(内存和CPU)是不可忽略的 - 这将是如果你没有广泛使用迭代器... – 2011-05-18 16:17:01

2

这将是低效的,因为容器需要A)提供另一个数据字段并且B)相应地更新它。然而,STL被认为是为了做不可能的事情,并将效率与抽象结合起来。 (它成功了,我要补充。)

如果你需要保持引用到不断变化的容器,要么使用一个不坏的迭代器(std::liststd::set)或保留指数为std::vector

3

迭代器失效的规则已经足够清晰,如果您按规则付费,则不应要求询问容器何时失效。

此外,迭代器并不总是同时失效。从矢量中移除一个元素只会使该元素和后一个元素无效。但是指向矢量开始的迭代器仍然有效。

在列表中,迭代器通常只在它们指向的特定节点被销毁时才会失效。

所以你不能问容器你的迭代器是否有效。

通用标准库实现可以选择启用与请求类似的附加运行时检查,尽管实现更复杂(它必须是为了正确)并且会损害性能。

+2

我不知道我同意“迭代器失效的规则是足够清晰的” 。似乎有很多规则。当你思考它们为什么是规则时,它们是有意义的,但是当操作会使迭代器失效(以及哪些迭代器可能失效)时,它仍然不总是清楚明显。正如你的下两点所指出的... – 2011-05-18 16:25:08

+1

@Micheal:而且规则也不是过于具体。添加到矢量_might_将所有迭代器无效化为它。 (当然,我可以检查它的容量和尺寸,但这正是OP询问的内容。) – sbi 2011-05-18 16:32:57

+0

确实如此,但有一个明显的折衷。你使用迭代器越聪明和“安全”,你得到的性能就越差,它们的通用性就越低(使得指针的行为就像那样),而且越难实现(并且正确地实现)。规则有时只会告诉你“你的迭代器*可能会失效”,但它们足够清晰,可以与它们一起工作 – jalf 2011-05-18 16:36:22

2

这在技术上是可行的,并且MSVC实现了提供类似功能的'checked iterators'(http://msdn.microsoft.com/en-us/library/aa985965.aspx)。尽管当迭代器失效时你没有得到通知,并且你不能直接查询状态(我知道的),但是抛出异常并且/或者调用invalid_parameter,这取决于构建的配置。

但是,它绝对是非标准的,并且显着提高了性能。这对调试很有用。

0

容器是否可以通过添加一个调用has_changed()来宣告它已经改变了?

我想,是的,它可以实施。这是一个非常简单的尝试(不那么优雅,但仍然):

bool has_changed(std::vector<int> &v) 
{ 
    static std::map<void*, size_t> has_changed_info; 
    void *ptr = &v; 
    std::map<void*, size_t>::iterator it = has_changed_info.find(ptr); 
    if (it == has_changed_info.end()) 
    { 
     has_changed_info[ptr] = v.capacity(); 
     return false; 
    } 
    if (it->second != v.capacity()) 
    { 
     it->second = v.capacity(); 
     return true; 
    } 
    return false; 
} 

测试代码:

int main() { 
     std::vector<int> v; 
     has_changed(v); 
     for (int i = 0 ; i < 100 ; ++i) 
     { 
      v.push_back(i); 
      if (has_changed(v)) 
        cout << "changed at " << i << endl; 
     }  
     return 0; 
} 

输出(GCC-4.3.4):

changed when inserting i = 0 
changed when inserting i = 1 
changed when inserting i = 2 
changed when inserting i = 4 
changed when inserting i = 8 
changed when inserting i = 16 
changed when inserting i = 32 
changed when inserting i = 64 

在线演示:http://www.ideone.com/QmO5q