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

Fau*_*lva 28 c++ iterator stl

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

Mor*_*enn 41

简短的回答

许多人认为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评论并提供措辞]

这个问题基本上类似于N3198unary_function所描述的用于消除衍生和朋友的要求的解决方案,我也非常赞成在这里遵循这种精神.我想补充一点,基本上所有"较新的"迭代器类型(例如相关的迭代器)都不是从它们中派生出来的.regexstd::iterator

该文引用了N3198,它本身表明它遵循N3145中讨论的弃用.弃用仅存在以提供typedefs 的类的原因如下:

我们对概念的经验使我们相信,如果类型和函数的可用性足够,则很少需要依赖于特定的基类派生类关系.即使在没有语言支持的概念的情况下,新的语言工具也可以推断出类类型中类型名称的存在,这会在它们之间引入更弱的耦合.通过关联类型替换继承的另一个优点是,这将减少出现歧义的情况的数量:如果类型将继承unary_functionbinary_function(这是有意义的,如果一个仿函数都是一元的话),这很容易发生和二进制函数对象).

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


更新: 来自N4245的问题2438似乎与我之前断言的内容相矛盾:

为了方便LWG,九个STL迭代器被描述为派生std::iterator他们的iterator_category/等.类型定义.不幸的是(并且无意中),这也要求继承,这是可观察的(不仅仅是通过is_base_of,而且还有重载决策).这是不幸的,因为它会让用户感到困惑,他们可能被误导认为他们自己的迭代器必须从中派生出来std::iterator,或者重载函数要采用std::iterator某种方式有意义.这也是无意的,因为STL最重要的迭代器(容器迭代器)不需要派生自std::iterator.(有些甚至被允许成为原始指针.)最后,这会不必要地限制实施者,他们可能不想从中衍生出来std::iterator.(例如,简化调试器视图.)

总而言之,我错了,@ aschepler是对的:它可以使用,但它绝对不是必需的 - 它也不会气馁.整个"let's remove std::iterator"事物存在于标准中,不限制标准库实现者.


第3轮: P0174R0建议弃用std::iterator以备将来可能的删除.该提案已经很好地解释了为什么它应该被弃用,所以我们继续:

对于读者而言,很长的void参数序列不仅仅是简单地在类定义本身中提供预期的typedef,这是当前工作草案采用的方法,遵循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 ...
};
Run Code Online (Sandbox Code Playgroud)

单凭清晰度的原因足以说服LWG更新标准库规范,不再要求标准迭代器适配器从std :: iterator派生,因此在标准本身内不再使用此模板.因此,它看起来像是一个强烈的弃用候选人.

这变得有点累,并不是每个人都同意,所以我会让你得出自己的结论.如果委员会最终决定std::iterator应该弃用,那么很明显你不应该再使用它了.请注意,后续文件强调了对删除以下内容的大力支持std::iterator:

2016年杰克逊维尔更新:

民意调查:弃用iteratorC++ 17 ??
SF F N A SA
6 10 1 0 0

另外,在上述投票结果,SF,˚F,Ñ,SA代表强有关,对于,中性,反对强烈反对.

2016年奥卢更新:

民意调查:仍希望弃用std::iterator?
SF FNA SA
3 6 3 2 0

P0619R1建议std::iterator尽可能快地删除C++ 20,并且还建议增强std::iterator_traits它以便它可以自动推断出类型difference_type,pointer以及当它们没有明确提供时reference的方式std::iterator.

  • 我不读任何这个,因为建议用户定义的类不应该继承`std :: iterator`.N3931的要点是标准库规范不应该要求*库类*继承它.在`unary_function`和`binary_function`的情况下,确定整个模型不如SFINAE方法,`std :: bind`等,类型,函数和所有.如果您要编写迭代器类,在定义所有正确的typedef并支持正确的表达式之前,您还没有真正这样做过.`std :: iterator`只是让这项工作更轻松的一种方法. (5认同)

Fre*_*Foo 8

如果你的意思是std::iterator:是的,这就是它的用途.

如果你的意思是别的:不,因为没有STL迭代器有virtual析构函数.它们不是为了继承,而从它们继承的类可能无法正常清理.

  • 这个建议过于宽泛.如果派生类不需要任何清理(自动生成或以其他方式),或者如果它不会以多态方式使用,则可以不使用虚拟析构函数.指向迭代器的指针极为罕见. (3认同)
  • 迭代器不在上下文中使用是重要的(即你从来没有指向派生迭代器的基本cass指针).迭代器总是对象. (2认同)

Arm*_*yan 5

如果你在谈论std::iterator模板,那么是的,你应该,但我希望你明白它没有功能,只是一堆typedef.这个决定的专家是你的迭代器可以被送到iterator_traits模板.

另一方面,如果你正在谈论一些特定的STL迭代器,vector<T>::iterator或者其他,那么答案就是响亮的NO.更不用说其他一切,你不确定它实际上是一个类(例如同样vector<T>::iterator可以只是类型定义T*)


Alo*_*ave 5

没有人不应该因为可能遇到的潜在问题.可能你最好使用Composition而不是继承 STL迭代器.

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

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

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

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

C++标准没有规定要求std::vector<T>::iterator不使用继承抑制技术来防止派生.因此,如果从STL迭代器派生,则依赖于STL的一个特性,该特性恰好允许派生.这使得这种实现不可移植.

如果没有正确实现错误行为:
考虑从派生迭代器类派生,如:

class yourIterator : std::vector<T>::iterator { ... };
Run Code Online (Sandbox Code Playgroud)

可能有一个函数在向量迭代器上运行,
例如:

void doSomething(std::vector<T>::iterator to, std::vector<T>::iterator from);
Run Code Online (Sandbox Code Playgroud)

因为yourIteratorstd::vector<T>::iterator你可以调用doSomething()你的容器类,但你将面临丑陋的问题Object Slicing.该doSomething()在适当的模板化的方式来实现,以避免此问题.

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

对于Ex:

yourIterator a;
yourIterator b;
...
std::transform( a++, b--, ... );
Run Code Online (Sandbox Code Playgroud)

后缀operator ++返回a std::vector<T>::iterator而不是 yourIterator导致选择了错误的模板.

因此,继承STL迭代器确实是可能的,但如果你准备好挖掘所有这些和许多其他潜在问题并解决它们,我个人不会给它时间和努力.