我可以/应该从STL迭代器继承
Can/Should I inherit from an STL iterator?
我可以/应该继承STL迭代器来实现我自己的迭代器类吗?如果没有,为什么不呢?
简短回答
许多人认为,与常规类型别名相比,类std::iterator
提供的不多,甚至通过不显式提供名称并依赖于模板参数的顺序来模糊它们。它在C++17中被弃用,可能几年后就会消失。
这意味着您不应该再使用std::iterator
了。如果你对整个故事感兴趣,你可以阅读下面的整篇文章(因为它是在反对提案之前开始的,所以有点多余)。
传统答案
如果你对历史不感兴趣,可以忽略下面的所有内容。以下片段甚至多次自相矛盾
到目前为止(C++11/C++14),该标准似乎意味着从std::iterator
继承以实现自定义迭代器不再是一个好主意。以下是N3931:的简要说明
尽管标准已经犯了十几次这个错误,但我建议不要将
directory_iterator
和recursive_directory_iterator
描述为源自std::iterator
,因为这是实现的绑定要求。相反,它们应该被描述为具有适当的typedef,并由实现者来决定如何提供它们。(is_base_of
用户可以观察到这种差异,而不是他们应该问这个问题。)[2014-02-08 Daniel评论并提供措辞]
这个问题基本上类似于N3198所描述的用于删除从
unary_function
和朋友那里派生的要求的解决方案,我强烈赞成在这里也遵循这种精神。我想补充一点,基本上所有"较新"的迭代器类型(如与regex
相关的迭代程序)也不是从std::iterator
派生的。
该论文引用了N3198,N3198本身表示它遵循了N3145中讨论的折旧。弃用仅用于提供typedef
的类的原因如下:
我们的概念经验让我们相信,如果类型和函数的可用性足够,那么很少有必要依赖特定的基类派生的类关系。即使在没有语言支持的概念的情况下,新的语言工具也允许我们推断类类型中类型名的存在,这将在它们之间引入弱得多的耦合。用关联类型替换继承的另一个优点是,这将减少出现歧义的情况:如果一个类型同时继承
unary_function
和binary_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
在上述民意调查结果中,SF、F和N代表强烈支持,支持,中立。
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_type
、pointer
和reference
,当它们没有明确提供时。
如果你的意思是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);
由于yourIterator
是std::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*
)
- 成员函数不能为集合迭代器和const_iterator的输入重载(但可以为其他 STL 迭代器重载)
- C++如何获取传递给函数(STL 迭代器)的参数的名称
- 如何将 stl 迭代器与特征一起使用?
- 用于 STL 迭代器、指针和 std::nullptr_t 的模板函数
- STL 迭代器继承:"value_type"不命名类型
- 错误试图实现STL迭代器
- 使用 stl 迭代器封装向量是否很好?如果是?怎么可能呢?
- 如何断言模板参数的类型 STL 迭代器类型
- 带有原始指针的stl::迭代器
- STL 迭代器:断言错误
- 多态性中的通用STL迭代器
- 使用STL迭代器的两个循环
- 如何在c++中对stl迭代器进行平均运算
- 如何使用STL迭代器和reverse_iterator处理数据
- DEV-C++5.11 STL迭代器的调试问题
- std::map::begin() 之前的 STL 迭代器
- 我是否可以轻松覆盖 (STL) 迭代器的类别?
- 如何使用 STL 迭代器处理结构
- STL 迭代器和数组的关系
- 使用c++stl迭代器而不是传统指针的正确方法是什么