C++ 什么';sentinel和end迭代器的区别是什么?
在阅读埃里克·尼布勒的作品时,C++ 什么';sentinel和end迭代器的区别是什么?,c++,iterator,range,sentinel,c++17,C++,Iterator,Range,Sentinel,C++17,在阅读埃里克·尼布勒的作品时, 我遇到了sentinel这个术语,它代替了end迭代器。 我很难理解sentinel优于end迭代器的好处。 有人能提供一个明确的例子,说明sentintel给表带来了什么,而这是标准迭代器对无法做到的 “sentinel是过去结束迭代器的抽象。sentinel是 可用于表示范围结束的常规类型。a sentinel和表示范围的迭代器应相等可比。 当迭代器i与 哨兵,我指的是那个元素 我认为Sentinel作为函数用于确定范围的结束,而不仅仅是位置?Sentinel
我遇到了sentinel这个术语,它代替了end迭代器。
我很难理解sentinel优于end迭代器的好处。
有人能提供一个明确的例子,说明sentintel给表带来了什么,而这是标准迭代器对无法做到的 “sentinel是过去结束迭代器的抽象。sentinel是 可用于表示范围结束的常规类型。a sentinel和表示范围的迭代器应相等可比。 当迭代器i与 哨兵,我指的是那个元素
我认为Sentinel作为函数用于确定范围的结束,而不仅仅是位置?Sentinel和结束迭代器的相似之处在于它们标记范围的结束。它们在如何检测这一目的方面有所不同;要么测试迭代器本身,要么在迭代器上测试数据值。如果您已经在对数据执行测试,sentinel可以允许您的算法“免费”完成,而无需任何其他测试。这既可以简化代码,也可以使其更快 一个非常常见的哨兵是用来标记字符串结尾的零字节。不需要为字符串的结尾保留单独的迭代器,它可以在处理字符串本身的字符时确定。这种约定的缺点是字符串不能包含零字符
请注意,我在阅读链接中的提案之前写下了这个答案;这是sentinel的经典定义,可能与此处提出的定义不一致。sentinel只允许末端迭代器具有不同的类型 对过期迭代器允许的操作是有限的,但这并没有反映在它的类型中。使用
*
a.end()
迭代器是不合适的,但是编译器会允许您使用
哨兵没有一元解引用或++
,等等。它通常与最弱的迭代器一样受到限制,即超过末端迭代器的迭代器,但在编译时强制执行
这是有回报的。通常,检测结束状态比找到它更容易。使用sentinel,==
可以在编译时而不是运行时调度以“检测另一个参数是否超过结束”
结果是,一些过去比C等效代码慢的代码现在编译到C级速度,例如使用std::copy
复制以空结尾的字符串。如果没有sentinel,您要么扫描以找到副本之前的结束,要么传入带有bool标志的迭代器,该标志表示“我是结束sentinel”(或等效物),并在=
上检查它
在使用基于计数的范围时,还有其他类似的优点。此外,zip ranges1之类的东西变得更容易表达(末端zip sentinel可以保存两个源sentinel,如果其中一个sentinel保存,则返回相等值:zip迭代器要么只比较第一个迭代器,要么比较两个)
另一种思考方式是,算法倾向于在作为结束迭代器的过去传递的参数上不使用迭代器概念的全部丰富性,并且迭代器在实践中的处理方式不同。Sentinel意味着调用方可以利用这一事实,这反过来让编译器更容易利用它
1拉链系列是指当您从2个或更多系列开始,然后像拉链一样将它们“拉链”在一起时得到的。范围现在位于各个范围元素的元组之上。推进zip迭代器将推进每个“包含的”迭代器,对于解引用和比较也是如此。引入sentinel的主要动机是,有很多迭代器操作是受支持的,但对于end迭代器
end()
,通常不需要这些操作。例如,通过*end()
取消对它的引用,通过++end()
增加它,等等(*)几乎没有任何意义
相比之下,end()
的主要用法只是将其与迭代器it
进行比较,以表明it
是否位于它刚刚迭代的事物的末尾。而且,与编程中的通常情况一样,不同的需求和不同的应用建议了一种新的类型
range-v3库将此观察转化为一个假设(通过一个概念实现):它为end()
引入了一个新类型,只要求它与相应的迭代器相等,但不需要通常的迭代器操作)。这种新类型的end()
称为哨兵
这里的主要优点是获得了抽象和更好的关注点分离,在此基础上编译器可能能够执行更好的优化。在代码中,基本思想是这样的(这只是为了解释,与range-v3库无关):
看到抽象了吗?现在,您可以在is_at_end
功能中执行您想要的任何检查,例如:
- 永不停止(获得无限范围)
- 在
增量后停止(以获得计数范围)N
- 遇到
时停止,即\0
(用于在C字符串上循环)*它='\0'
- 12点时停止(吃午饭),以此类推
N
视为编译时参数)。在这种情况下,编译器可能能够更好地优化代码
(*)注意,这并不意味着这类操作通常没有用处。例如,
--end()。然而,实现标准库似乎是可能的
struct my_iterator; //some iterator
struct my_sentinel
{
bool is_at_end(my_iterator it) const
{
//here implement the logic when the iterator is at the end
}
};
auto operator==(my_iterator it, my_sentinel s) //also for (my_sentinel s, my_iterator it)
{
return s.is_at_end(it);
}