取消引用无效迭代器时强制执行异常

时间:2016-10-08 18:22:29

标签: c++ stl

最近我发现一个巨大的代码中有一个错误,因为在集合为空时解除引用set.begin()。是否有一种方法(比如设置编译器标志)强制容器在解除引用无效迭代器(例如空集.begin().end()

)时抛出异常
std::set<int> s;
*s.begin(); // force this to throw an exception because std::set is empty

2 个答案:

答案 0 :(得分:1)

  

set.begin()永远不会抛出异常。我想知道是什么原因。

空容器是一个完全有效的容器,对它们执行算法是完全合理的。如果:

,这将是非常令人惊讶的
if (std::find(c.begin(), c.end(), v) == c.end()) {
   // not present
}

对于包含一个或多个不包含v的元素的容器工作正常,但如果它为空则抛出异常!容器为空时,例外不是例外。那将是疯狂的。这将要求每个程序员在每个点特别检查空虚作为一个特例 - 当它不是真的。

规则就是取消引用end()迭代器是未定义的行为。对于空容器begin() == end(),所以也延伸到begin()。您的代码可以控制这些访问。

  

还有一种方法(比如设置编译器标志)强制容器在解除引用无效迭代器(例如空集.begin().end()

如果您尝试取消引用无效迭代器(例如libstdc++),某些实现将断言。但是你总是可以简单地编写一个包装器实现来为你做这个:

#ifdef NDEBUG
    template <class T>
    using my_set = std::set<T>;
#else
    template <class T>
    class my_set {
        // implement your own set that carefully manages all the lifetimes
        // of its entries such that it's possible to check the validity
        // of them in the iterators, and then throw on bad dereference
    };
#endif

答案 1 :(得分:1)

没有任何特定的迭代器以某种方式宣告是否可以有效地解除引用迭代器。 C ++不是基于虚拟机的代码,如Java或C#,虚拟机跟踪每个对象的有效性。

可能存在特定于编译器的选项,以启用额外的运行时完整性检查。但是既然你还没有确定你的编译器,那么这里的答案就是“检查编译器的文档”。

如果没有一个选项适合你,答案就是“自己写”。例如,基于编译时宏并使用某些编码约定,可以实现与迭代器兼容的接口,以执行额外的健全性检查。例如,而不是声明

std::set<int> set_of_ints;

std::set<int>::iterator b=set_of_ints.begin();

// Or something else that references std::set<int>::iterator

声明并始终使用别名:

typedef std::set<int> set_of_ints_t;

set_of_ints_t set_of_ints;

set_of_ints_t::iterator p=set_of_ints.begin();

......等等。有了这种编码约定,就可以很容易地使用编译时宏来进行额外的健全性检查:

#ifndef DEBUG
    typedef std::set<int> set_of_ints_t;
#else
    typedef my_sanity_checked_set sets_of_ints_t;
#endif

my_sanity_checked_set是一个与std::set<int>接口兼容的自定义容器,以及一个迭代器,其运算符对每个操作执行额外的健全性检查(例如,不递增或递减过去集合的边界,解除引用end()值等...)

当然,所有这些检查都需要额外的开销。您可以在开发期间使用它,然后关闭整个过程并使用本机std::set进行编译以进行发布构建。这就是它的完成方式。