【问题标题】:Arithmetic on end() iteratorend() 迭代器的算术运算
【发布时间】:2014-11-13 17:33:18
【问题描述】:

让A成为std::vector<double>

这是明确定义的吗?

if(!A.empty())
    std::vector<double>::iterator myBack = A.end() - 1;

end 迭代器是否仅适用于等式和不等式检查?或者只要我留在容器中,我就可以执行一些指针运算?

在我的平台上,此代码有效。我想知道这是否是便携式的。

【问题讨论】:

  • 这很好,你也可以使用A.rbegin()
  • 迭代器算术。对于双向迭代器应该没问题(如std::vector::iterator)。

标签: c++ stl iterator


【解决方案1】:

这是完全有效的,因为vector::iterator 是一个随机访问迭代器。您可以对其执行算术运算,它不依赖于平台。

std::vector<double>::iterator it = A.end();
while (it != A.begin()){
    --it; //this will skip A.end() and loop will break after processing A.front()
    //do something with 'it'
}

但是A.end() 指的是理论上的过去的元素,所以它不指向一个元素,因此不应被取消引用。所以最佳实践是使用反向迭代器而不是递减结束迭代器。

for(std::vector<double>::reverse_iterator it = A.rbegin(); it != A.rend(); ++it) {
    //do something with 'it'
}

这两个循环做同样的事情,第二个是可以理解的,更干净的方式。

【讨论】:

  • 两个循环不相同,第一个循环不会遍历A.front()
  • 但我认为A.begin() - 1 无效。
  • 只是为了澄清:随机访问迭代器要求添加了 OP 中使用的二进制 - 运算符,但 -- 的可递减性和有效性是由于双向迭代器的一般要求。
【解决方案2】:

如果您注意一些特殊情况,这几乎是安全的:

A.end() 为您提供了一个迭代器,表示刚好超出std::vector 末尾的位置。您应该尝试取消引用它。

如果向量有零个元素,则 A.end() - 1明确定义的。在所有其他情况下,只要您在容器边界内,您确实可以执行指针算术。请注意,该标准保证 std::vector 数据是连续的,并且以与包含类型的 C++ 数组完全相同的方式打包。唯一的例外是std::vector&lt;bool&gt;,由于标准指定的紧密封装专业化,它的行为有所不同。 (请注意,sizeof(bool)保证具有特定的标准值)。

如果我是你,我会使用 A.rbegin() 访问最右边的元素并在继续之前检查返回值并坚持使用迭代器公式。很容易忘记std::vector&lt;bool&gt; 专业化。

【讨论】:

  • 我不确定bool 的情况。这不是迭代器——不是指针——算术,就像@Jarod42 指出的那样吗?对于std::vector&lt;bool&gt;,这难道不应该使其完全安全且定义明确吗?
  • 如果 std::vector&lt;bool&gt; 恰好有 3 个元素,那么我不相信 A.end() - 1 是在 sizeof(bool) == sizeof(int) 时定义的。不过我可能是错的。但是,如果您将任何内容投射到bool*,那么您肯定会遇到麻烦。 bool 反向迭代器上的重载运算符可能会处理这个问题。简短的回答:我不知道。我正在考虑悬赏这个问题。
  • 如果std::vector&lt;bool&gt;::iterator 确实像您所说的那样,对向量的简单迭代将不起作用。只要您在迭代器上进行操作,- 1 就必须将其移动一个逻辑元素,而不是 sizeof(bool) 字节。我在ideone.com/2pAiE1 上放了一些代码,只要减法的结果在容器内,它就可以按我的预期工作。您应该使用_GLIBCXX_DEBUG(或您的编译器的等效项)对其进行编译,以便在您越界时让它向您吠叫。
  • 但是,是的,如果你抛弃迭代器并改用bool*,你肯定有麻烦了:)
  • @Magnus Hoff 我检查了我的 STL 的源代码(我知道不是确定的),你是正确的。稍后我会挖掘标准草案。幸运的是,我的回答仍然成立,并且我相信我已经充分警告不要使用 pointer,而不是 iterator 算术。
【解决方案3】:

我意识到这个问题有点老了,但我被指示到这里是关于 end() - 1 的,我发现现有的答案和 cmets 信息丰富且合理,但由于缺乏引用而无法令人信服,而且我不确定是否它们特定于vector。所以我挖掘了尽可能多的具体信息来说服自己这里的答案是正确的。

这篇文章代表我为确认答案所做的研究,基本上是我的笔记,但我试图使其尽可能连贯,我认为它可能有用。如果此处有任何问题,请非常感谢进行更正。


重申答案

这里的TL;DR是肯定的,这里的答案是正确的,不仅适用于vector,在更一般的情况下也是如此:

如果容器的迭代器类型满足BidirectionalIterator(并因此提供递减操作),那么对于任何容器类型,以下将始终有效,其中e被初始化为返回值container.end():

  • 如果!container.empty()--e 有效。
  • 如果!container.empty()++(--e) == container.end() 为真。

如果迭代器也满足RandomAccessIterator,那么这些更一般的陈述是有效的:

  • e - ne -= n 用于 [ 0, container.size() ] 中的任意整数 n
  • e + ne += n 用于 [ - container.size() , 0 ] 中的任何整数 n

因此,OP 中的 vector 示例不仅很好,正如其他答案所指出的那样,而且它定义明确,保证适用于任何容器类型。


推理

所以现在这是我觉得缺少的一点。一、从Container的要求:

expression return type semantics conditions complexity
a.end() (const_)iterator iterator to one past the last element of a Constant

这表示“过去最后一个元素”。然而,这是否意味着end() 可以递减?我们需要确定。以下项目在这里很重要,我已将它们编号以供参考:

  1. Container:“end()a 结束后返回一个”要求。
  2. RandomAccessIteratori - n,根据-=定义,没有给出限制。
  3. RandomAccessIteratorr -= n,根据+=定义,没有给出限制。
  4. RandomAccessIteratorr += n,根据--r 定义n &lt; 0,没有给出限制。
  5. BidirectionalIterator: --a
    • 前提条件:a 是可递减的 → 存在 b 使得a == ++b
    • 后置条件:a 是可取消引用的。
    • 后置条件:--(++a) == a
    • 后置条件:如果--a == --b 那么a == b
    • 后置条件:a--a 是同一个迭代器实例。
  6. BidirectionalIterator注意:“双向迭代器不一定非要可取消引用才能递减(特别是,结束迭代器不可取消引用但可递减)”。
  7. Container:声明size() 在语义上等同于std::distance(begin(), end())
  8. distance:返回从firstlast增量数

分解:

(5) 的前提条件是--a 要工作,a 必须是可递减的,并继续定义迭代器 a 如果存在 b 使得 ++ b == a 是可递减的。

(1) 的“最后一个”语言似乎暗示如果 b 是容器中最后一个元素的迭代器,那么 ++ b == end() .然而,更令人信服的是,(7) 表明std::distance(begin(), end()) 必须工作,因此(8) 意味着begin() 返回的迭代器必须能够重复递增直到等于end(),这意味着对于非-空容器,在某些时候必须存在一个 b 使得++ b == end()

然后,将这两者结合起来表明,如果!empty()end() 总是可递减的,因为总是有一个 b 使得++ b == end()(否则distance(begin(), end()) - 因此@987654381 @——不符合其语义要求),这是可递减性的定义。另请注意,(6) 明确指出可递减的迭代器不需要可取消引用,并说明了结束迭代器的可递减性。

此外,由于end()!empty()时是可递减的,那么(其中e被初始化为container.end()的返回值):

  • -- e 有效,来自 (5)。
  • e += nn &lt;= 0 有效,来自 (4)。
  • e -= nn &gt;= 0 有效,来自 (3)。
  • e - nn &gt;= 0 有效,来自 (2)。
  • 由于+=-=-(对于上面指出的 n 的符号)都是根据重复应用 -- 的语义定义的,这限制了 n 在容器的大小范围内,因为 begin() 不可递减(根据可递减性的定义),最终迭代器必须命中 begin()

因此,只要在应用它的迭代器之前至少有 1 个元素,OP 中的 - 1 就有效(来自 (2))。

可递减性与可取消引用性:请注意,这是有区别的。 (6) 指出概念是分开的。可递减性意味着--i 是有效的,可取消引用性意味着*ii-&gt; 是有效的。在 OP 的 vector 示例中,虽然 end() 可递减,但不可取消引用(vector::end() 明确说明了这一点)。


代码

哦,是的,我还写了a test program 只是为了检查一下:

#include <boost/core/demangle.hpp>
#include <version>
#if __has_include(<array>) && (__cplusplus >= 201103L)
#  include <array>
#  define HAVE_ARRAY 1
#endif
#include <vector>
#include <deque>
#include <list>
#include <set> // + multiset
#include <map> // + multimap
#if __has_include(<span>) && (__cpp_lib_span >= 202002L)
#  include <span>
#  define HAVE_SPAN 1
#endif
#include <typeinfo>
#include <cassert>
#include <cstdio>

#if (__cpp_constexpr < 200704L)
#  define constexpr
#endif

using namespace std;

constexpr const int MAGIC = 42;

int extract (const int &v) {
    return v;
}

int extract (const pair<int,int> &v) {
    assert(v.first == v.second);
    return v.first;
}

template <typename C> struct decrementable_end_tester {
    C container;
    decrementable_end_tester ();
    void test () {
        printf("%s...\n", boost::core::demangle(typeid(C).name()).c_str());
        assert(!container.empty());
        {
            typename C::iterator b = container.begin();
            typename C::iterator e = container.end();
            assert(b == --e);
            assert(extract(*e) == MAGIC);
            assert(container.end() == ++e);
        }
        {
            typename C::iterator b = container.begin();
            typename C::iterator e = container.end(); 
            assert(e == ++b);
            assert(container.begin() == --b);
            assert(extract(*b) == MAGIC);
        }
    }
};

// i thought templating that would make initialization simpler but i'm not really
// that great with templates so i dunno if i got the most out of it:

template <typename C> decrementable_end_tester<C>::decrementable_end_tester () {
    container.insert(container.end(), MAGIC);
}

#if HAVE_ARRAY
template <> decrementable_end_tester<array<int,1> >::decrementable_end_tester () {
    container[0] = MAGIC;
}
#endif

#if HAVE_SPAN
static int span_buffer = ~MAGIC;
template <> decrementable_end_tester<span<int,1> >::decrementable_end_tester () 
    : container(&span_buffer, 1)
{
    container[0] = MAGIC;
}
#endif

template <> decrementable_end_tester<map<int,int> >::decrementable_end_tester () {
    container.insert(make_pair(MAGIC, MAGIC));
}

template <> decrementable_end_tester<multimap<int,int> >::decrementable_end_tester () {
    container.insert(make_pair(MAGIC, MAGIC));
}

int main () {
    // forward_list, unordered_([multi](set|map)) don't use bidirectional iterators.
#if HAVE_ARRAY
    decrementable_end_tester<array<int,1> >().test();
#endif
    decrementable_end_tester<vector<int> >().test();
    decrementable_end_tester<deque<int> >().test();
    decrementable_end_tester<list<int> >().test();
    decrementable_end_tester<set<int> >().test();
    decrementable_end_tester<multiset<int> >().test();
    decrementable_end_tester<map<int,int> >().test();
    decrementable_end_tester<multimap<int,int> >().test();
#if HAVE_SPAN
    decrementable_end_tester<span<int,1> >().test();
#endif
}

应该在不触发任何断言的情况下运行。


我希望这会有所帮助。几乎所有这些都是我努力说服自己end() - 1 确实有效

【讨论】:

    猜你喜欢
    • 2020-10-05
    • 2012-07-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-10-24
    • 2013-11-25
    • 2019-01-30
    相关资源
    最近更新 更多