我可以/应该从STL迭代器继承

Can/Should I inherit from an STL iterator?

本文关键字:STL 迭代器 继承 我可以      更新时间:2023-10-16

我可以/应该继承STL迭代器来实现我自己的迭代器类吗?如果没有,为什么不呢?

简短回答

许多人认为,与常规类型别名相比,类std::iterator提供的不多,甚至通过不显式提供名称并依赖于模板参数的顺序来模糊它们。它在C++17中被弃用,可能几年后就会消失。

这意味着您不应该再使用std::iterator了。如果你对整个故事感兴趣,你可以阅读下面的整篇文章(因为它是在反对提案之前开始的,所以有点多余)。


传统答案

如果你对历史不感兴趣,可以忽略下面的所有内容。以下片段甚至多次自相矛盾

到目前为止(C++11/C++14),该标准似乎意味着从std::iterator继承以实现自定义迭代器不再是一个好主意。以下是N3931:的简要说明

尽管标准已经犯了十几次这个错误,但我建议不要将directory_iteratorrecursive_directory_iterator描述为源自std::iterator,因为这是实现的绑定要求。相反,它们应该被描述为具有适当的typedef,并由实现者来决定如何提供它们。(is_base_of用户可以观察到这种差异,而不是他们应该问这个问题。)

[2014-02-08 Daniel评论并提供措辞]

这个问题基本上类似于N3198所描述的用于删除从unary_function和朋友那里派生的要求的解决方案,我强烈赞成在这里也遵循这种精神。我想补充一点,基本上所有"较新"的迭代器类型(如与regex相关的迭代程序)也不是从std::iterator派生的。

该论文引用了N3198,N3198本身表示它遵循了N3145中讨论的折旧。弃用仅用于提供typedef的类的原因如下:

我们的概念经验让我们相信,如果类型和函数的可用性足够,那么很少有必要依赖特定的基类派生的类关系。即使在没有语言支持的概念的情况下,新的语言工具也允许我们推断类类型中类型名的存在,这将在它们之间引入弱得多的耦合。用关联类型替换继承的另一个优点是,这将减少出现歧义的情况:如果一个类型同时继承unary_functionbinary_function,则很容易发生这种情况(如果函子既是一元函数对象又是二元函数对象,则这是有意义的)。

tl;dr:只提供typedef的类现在被认为是无用的。此外,当不需要时,它们会增加耦合,更加冗长,并且在某些情况下可能会产生不必要的副作用(请参阅前面的引用)。


更新:N4245中的2438问题似乎实际上与我之前断言的内容相矛盾:

为了便于LWG,九个STL迭代器被描述为从std::iterator派生以获得它们的iterator_category/等。typedefs。不幸的是(无意中),这也要求继承,这是可以观察到的(不仅通过is_base_of,还通过过载解决)。这是不幸的,因为它混淆了用户,用户可能会被误导,认为他们自己的迭代器必须从std::iterator派生,或者重载函数以获取std::iterator在某种程度上是有意义的。这也是无意的,因为STL最重要的迭代器容器迭代器不需要从std::iterator派生。(有些甚至被允许作为原始指针。)最后,这不必要地限制了实现者,他们可能不想从std::iterator派生。(例如,为了简化调试器视图。)

总之,我错了,@aschepler是对的:它可以使用,但它肯定不是必需的-也不气馁。整个"让我们移除std::iterator"的内容存在于标准中,而不是约束标准库的实现者。


第三轮:P0174R0建议弃用std::iterator,以备将来可能移除。该提案已经很好地解释了为什么应该否决它,所以我们开始:

与在类定义本身中简单地提供预期的typedef相比,void参数的长序列对读者来说要不那么清楚,这是当前工作草案所采用的方法,遵循C++14中设置的模式,在C++14中,我们反对在整个函数库中从unary_function和binary_function派生。

除了清晰度降低之外,迭代器模板还为不小心的人设置了陷阱,因为在典型的使用中,它将是一个依赖基类,这意味着它在从类或其成员函数中查找名称时不会进行查看。这导致惊讶的用户试图理解为什么以下简单用法不起作用:

#include <iterator>
template <typename T>
struct MyIterator : std::iterator<std::random_access_iterator_tag, T> {
value_type data;  // Error: value_type is not found by name lookup 
// ... implementations details elided ...
};

仅出于明确性的原因就足以说服LWG更新标准库规范,不再强制要求标准迭代器adapators派生自std::迭代器,因此在标准本身中不再使用此模板。因此,它看起来像是一个有力的反对候选人。

这变得有点累,似乎不是每个人都同意,所以我让你自己得出结论。如果委员会最终决定弃用std::iterator,那么它将明确表示您不应该再使用它。请注意,后续论文强调了对删除std::iterator:的大力支持

杰克逊维尔更新,2016:

轮询:弃用C++17的iterator
SF F N A SA
6 10 1 0

在上述民意调查结果中,SFFN代表强烈支持支持中立。

Oulu更新,2016:

民意调查:仍想否决std::iterator?
SF F N A SA
3 6 3 2 0

P0619R1建议删除std::iterator,可能最快在C++20中删除,还建议增强std::iterator_traits,以便它可以像std::iterator那样自动推导类型difference_typepointerreference,当它们没有明确提供时。

如果你的意思是std::iterator:是的,那就是它的作用。

如果你的意思是:不,因为STL迭代器都没有virtual析构函数。它们不是用来继承的,从它们继承的类可能无法正确清理。

任何人都不应该这样做,因为可能会遇到潜在的问题。可能您最好使用Composition,而不是使用STL Iterators的继承

由于缺少虚拟析构函数而导致的未定义行为:
STL容器&迭代器并不意味着充当基类,因为它们没有虚拟析构函数。

对于没有虚拟析构函数用作基类的类,当通过指向基类的指针(delete、delete[]等)解除分配时会出现问题。由于类没有虚拟析构函数,因此无法正确清理它们,并导致"未定义的行为"。

有人可能会争辩说,不需要以多态方式删除迭代器&因此,从STL迭代器派生没有错,可能还有其他一些问题,比如:

继承可能根本不可能:
标准容器中的所有迭代器类型都是实现定义的
例如:std::vector<T>::iterator可能只是一个T*。在这种情况下,您根本无法从中继承。

C++标准没有要求std::vector<T>::iterator不使用遗传抑制技术来防止衍生。因此,如果您是从STL迭代器派生的,那么您就依赖于STL中恰好允许派生的功能。这使得这样的实现不可移植

如果未正确实现,则会出现错误行为:
请考虑您是从向量迭代器类派生的,如:

class yourIterator : std::vector<T>::iterator { ... };

可能有一个函数对向量迭代器进行操作,
例如:

void doSomething(std::vector<T>::iterator to, std::vector<T>::iterator from);

由于yourIteratorstd::vector<T>::iterator,您可以在容器类上调用doSomething(),但您将面临Object Slicing的丑陋问题。doSomething()必须以适当的模板化方式实现,以避免问题

使用标准库算法时的问题:
请考虑您使用的是从向量迭代器派生的,然后使用std::transform()等标准库算法

例如:

yourIterator a;
yourIterator b;
...
std::transform( a++, b--, ... );

后缀CCD_ 45返回CCD_CCD_ 47导致选择了错误的模板。

因此,从STL迭代程序继承确实是可能的,但如果你准备挖掘出所有这些和许多其他潜在的问题并解决它们,就我个人而言,我不会给它这样做的时间和精力。

如果您谈论的是std::iterator模板,那么是的,您应该这样做,但我希望您理解它没有功能,只有一堆typedef。这个决定的好处是可以将迭代器提供给iterator_traits模板。

另一方面,如果您谈论的是某个特定的STL迭代器,如vector<T>::iterator或其他迭代器的话,那么答案是响亮的NO。更不用说其他的了,你不确定它是否真的是一个类(例如,相同的vector<T>::iterator可以被类型化为T*)