【问题标题】:Testing whether an iterator points to the last item?测试迭代器是否指向最后一项?
【发布时间】:2011-03-31 18:43:27
【问题描述】:

我有一个由 std::find() 产生的 stl 迭代器,并希望测试它是否是最后一个元素。一种写法如下:

mine *match = someValue;
vector<mine *> Mine(someContent);
vector<mine *>::iterator itr = std::find(Mine.begin(), Mine.end(), match);

if (itr == --Mine.end()) {
  doSomething;
}

但在我看来,递减 end() 迭代器是自找麻烦,例如如果向量没有元素,那么它将是未定义的。即使我知道它永远不会是空的,它仍然看起来很丑。我在想也许 rbegin() 是要走的路,但不确定将正向迭代器与反向迭代器进行比较的最佳方法。

【问题讨论】:

  • 这里确实有些难看。但是,我建议这不是您确定itr 是否指向最后一个元素的方式。丑陋的是你需要知道itr 是否指向最后一个元素。
  • @John:你可能是对的。也许使用if (Mine.Length() != 0 &amp;&amp; &amp;Mine[Mine.Length()-1] == itr) 或类似的东西来检查会更有意义。
  • 嗯,我的回答也很丑。这应该是 GMan 的第一个建议:if (&amp;Mine.back() == itr)
  • @Steven:我敢打赌,如果我们知道这段代码的更大上下文,我们就能找出真正的问题并修复它。我有一个理论。如果代码拒绝被编写,那是因为它知道它是错误的。
  • @John:同样,你可能是对的。很多时候,有些事情很尴尬,因为它与更自然的做事方式作斗争。

标签: c++ stl iterator


【解决方案1】:

这样做:

// defined in boost/utility.hpp, by the way
template <typename Iter>
Iter next(Iter iter)
{
    return ++iter;
}

// first check we aren't going to kill ourselves
// then check if the iterator after itr is the end
if ((itr != Mine.end()) && (next(itr) == Mine.end()))
{
    // points at the last element
}

就是这样。永远不会给你未定义的行为,适用于所有迭代器,美好的一天。

为了好玩而总结一下:

template <typename Iter, typename Cont>
bool is_last(Iter iter, const Cont& cont)
{
    return (iter != cont.end()) && (next(iter) == cont.end())
}

给予:

if (is_last(itr, Mine))

如果您对实用功能/好看的代码过敏,请执行以下操作:

if ((itr != Mine.end()) && (itr + 1 == Mine.end()))

但是你不能在非随机访问迭代器上这样做。这个适用于双向迭代器:

if ((itr != Mine.end()) && (itr == --Mine.end()))

end() &gt; itr 开始,第一次检查是安全的。

【讨论】:

  • 关于反向迭代器:Mine.rbegin().base() == Mine.end();。反向迭代器将迭代器存储到取消引用时获得的下一个元素。你仍然需要先增加它。
  • 正如我在对 OP 的评论中提到的,我认为需要知道 itr 是否指向最后一个元素的整个想法是代码异味。有什么不对劲的。但是,如果你真的需要这样做,我会这样做。
  • 第一个是最接近正确的,除了它不必要地取消引用只是为了再次引用。另外,如果向量为空,back() 不会抛出吗?
  • @Michael:同上。我的 C++ 显然已经生锈了,这就是我对&amp;* 的误解。我没有错的一半是,这不是你应该在等于 end() 的迭代器上做的事情。
  • std::next 在 C++11 中可用
【解决方案2】:

更好的方法是复制迭代器然后递增它。然后,您可以针对end() 测试增加的版本。如果你很小心,你可以使用后增量来避免需要正式复制它。

  if (++vector<mine*>::iterator(itr) == Mine.end())

如果 itr 已经在末尾:

  if (itr == Mine.end() || ++vector<mine*>::iterator(itr) == Mine.end())

或者,根据 GMan 的回答,但更安全一点:

  if (Mine.Length() == 0 || itr == Mine.End() || &*itr == &Mine.back())

我刚刚又修复了最后一个,因为我对 &amp;* 的看法是错误的。

【讨论】:

  • @GMan:如果目标是确定迭代器是否指向最后一个有效元素,则此代码将始终如一地工作。如果存在已经指向最后一个有效元素的风险,您可以将谓词更改为首先测试是否为 itr == Mine.end(),并使用 OR。
  • @Steven:你的意思是“更好”为“与前向迭代器一起工作”?
  • @GMan:我的意思是更好,因为它适用于任何容器。
  • -1 用于卷积,没有额外的安全性,并且似乎不起作用。将原始的非递增值与end 进行比较有什么意义?
  • @Potatoswatter:你说得对:我只是将它修复为预先递增复制的迭代器而不是后递增。谢谢。
【解决方案3】:

这与从单链表中删除节点本质上是相同的问题。你必须有两个迭代器,一个在一个节点后面跟随另一个,所以当“转发”迭代器到达你想要删除的节点(或任何操作;在你的情况下,所需的节点将是结束),“ following" 迭代器指向之前的节点(在您的情况下,这将是最后一个节点)。

【讨论】:

  • 是的,当我说“如果你小心,你可以使用后增量来避免正式复制它的需要”时,这就是我语无伦次的暗示。只要您保留迭代器的先前值,您就可以预先增加当前值并使其可用,即使您对先前值进行操作也是如此。不过,正确设置有点棘手。
【解决方案4】:

为什么只有当项目是最后一项时才需要执行特殊行为?

这个呢。该计划只是将迭代器项的地址与容器中最后一项的地址进行比较,并检查以确保该项实际上还没有结束(使back 调用安全):

if (itr != Mine.end() && &*itr == &Mine.back()) {
  doSomething;
}

【讨论】:

  • 几乎是正确的,如果Mine.length() 为0,它将失败。
  • @Steven Mine.length() 如果在未结束的容器中找到一个有效的迭代器,它怎么能是 0?根据定义,迭代器指向一个项目,因此 length 不能为 0。还是我错过了一些明显的东西?
  • 好吧,如果没有找到,find 方法可以返回 Mine.end()
  • @Steven 我检查的第一部分我测试 find 没有返回 end 抓住了。
  • 你是对的。如果你编辑你的答案,我就可以投票。
【解决方案5】:

是的,如果向量可能为空,则减少(或增加)end 是不安全的。用指针做同样的事情甚至有点不安全,尽管你可能会侥幸逃脱。

为了真正安全,请使用已知安全有效的减法和值:

if ( Mine.end() - itr == 1 )

为了与所有前向迭代器兼容(例如在slist 中,与vectordeque 的随机访问迭代器相反),请使用

if ( std::distance( itr, Mine.end() ) == 1 )

或者如果您关心性能但有双向迭代器(包括任何 C++03 容器)

if ( itr != Mine.end() && itr == -- Mine.end() )

或者只有前向迭代器和 O(1) 时间的真实案例,

if ( itr != Mine.end() && ++ container::iterator( itr ) == Mine.end() )

或者如果你一心想避免命名迭代器类,

if ( itr != Mine.end() && ++ ( Mine.begin() = itr ) == Mine.end() )

【讨论】:

  • 这应该可以工作,虽然指针算术不是很清楚,但至少可以避免尝试取消引用空指针。
  • 您更改了答案以使其变得更糟:现在它仅适用于向量。
  • @Steven:之前是错的。这个问题只涉及向量。无论如何,编辑并没有改变它的兼容性,因为我只是消除了一个(通用)操作。
  • 我无法想象在链表上计算Mine.end() - itr 可能是有效的,即使它是合法的。
  • @Steven:我没有改变那部分。无论如何,我更新了兼容性选项。
【解决方案6】:

如果你这样做:

if(itr != Mine.end() && itr == --Mine.end())

应该没问题。因为如果 itr 不在末尾,那么容器中必须至少有 1 个元素,因此 end 在递减时必须产生一个值结果。

但是,如果您仍然不喜欢这样,有很多方法可以做一些等效的事情,正如所有其他答案所示。

这是另一种选择:

if(itr != Mine.end() && std::distance(Mine.begin(), itr) == Mine.size()-1)

【讨论】:

  • 在第二种解决方案中,如果容器是类似列表的,则必须遍历整个列表才能确定距离。
【解决方案7】:

这是另一个潜在的解决方案:

template<class Iterator, class Container> bool is_last(Iterator it, const Container& cont)
{
    // REQUIREMENTS:
    // the iterator must be a valid iterator for `cont`
    if( it == cont.end() )
        return false;   // or throw if you prefer
    return (++it) == cont.end();
}

【讨论】:

  • +1,没有看到你的答案,很抱歉踩到你的脚趾。不过,我们将函数签名集中在了高五。
  • 我不相信所有的控制路径都会返回一个值......应该有return false;作为函数的最后一行,对吧?
  • @John 您需要在模板函数中返回 false。
  • @GMan:我们确实做到了。至少我可以更确定我的签名是对的! :)
  • 最后三行可能就是return it == cont.end();
【解决方案8】:

您首先需要一种方法来determine if an iterator is a reverse one,即ingeniously shown here

#include <iterator>
#include <type_traits>

template<typename Iter>
struct is_reverse_iterator : std::false_type { };

template<typename Iter>
struct is_reverse_iterator<std::reverse_iterator<Iter>>
: std::integral_constant<bool, !is_reverse_iterator<Iter>::value>
{ };

那么你可以有两种方式来执行测试

template<bool isRev> // for normal iterators
struct is_last_it
{
    template<typename It, typename Cont>
    static bool apply(It it, Cont const &cont)
    { // you need to test with .end()
        return it != cont.end() && ++it == cont.end();
    }
};

template<> // for reverse iterators
struct is_last_it<true>
{
    template<typename It, typename Cont>
    static bool apply(It it, Cont const &cont)
    { // you need to test with .rend()
        return it != cont.rend() && ++it == cont.rend();
    }
};

和单一的接口函数

template<typename It, typename Cont>
bool is_last_iterator(It it, Cont const &cont)
{
    return is_last_it<is_reverse_iterator<It>::value>::apply(it, cont);
};

那么对于每种类型的迭代器(反向/直接)你都可以使用接口函数

int main()
{
    std::vector<int> v;
    v.push_back(1);

    auto it (v.begin()),  ite(v.end());   // normal iterators
    auto rit(v.rbegin()), rite(v.rend()); // reverse iterators

    std::cout << is_last_iterator(it, v) << std::endl;
    std::cout << is_last_iterator(ite, v) << std::endl;
    std::cout << is_last_iterator(rit, v) << std::endl;
    std::cout << is_last_iterator(rite, v) << std::endl;

    return 0;
}

请注意,一些实现(除了足够常见的std::begin()std::end() 之外,还包括std::rbegin()std::rend()。如果可能,请使用这组函数而不是成员.begin() 等。

【讨论】:

  • 从语言的角度来看一个漂亮的答案,但我想知道是否将所有这些标题都包含在任何地方,只是为了进行简单的检查(编译时间、学习曲线等)+1。
【解决方案9】:

试图使这个答案尽可能简单和通用:

if( itr!=Mine.end() && itr== --Mine.end())

如果迭代器不是双向的,

if( itr!=Min.end() && ++decltype(itr)(itr)==Mine.end())

第二个创建 itr 的临时副本并将其递增以针对结束迭代器进行测试。

在这两种情况下,第一个测试避免空容器触发未定义的情况。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2016-02-12
    • 2012-06-13
    • 2011-08-21
    • 2012-03-26
    • 2018-11-01
    • 2012-11-28
    • 1970-01-01
    相关资源
    最近更新 更多