我正在为考试而学习,但是我有一个问题很难解决。
为什么没有迭代器基类存在所有其他迭代器继承的东西?
我猜我的老师是从cpp参考“ http://prntscr.com/mgj542 ”中引用层次结构的,我们必须提供其他原因,而不是为什么?
我知道什么是迭代器(某种),并且它们用于在容器上工作。据我了解,由于底层数据结构可能不同,因此不同的容器具有不同的迭代器,例如,您可以随机访问数组,但不能访问链表,并且不同的容器需要不同的遍历它们的方式。
根据容器的不同,它们可能是专门的模板,对吗?
我正在为考试而学习,但是我有一个问题很难解决。
为什么没有迭代器基类存在所有其他迭代器继承的东西?
我猜我的老师是从cpp参考“ http://prntscr.com/mgj542 ”中引用层次结构的,我们必须提供其他原因,而不是为什么?
我知道什么是迭代器(某种),并且它们用于在容器上工作。据我了解,由于底层数据结构可能不同,因此不同的容器具有不同的迭代器,例如,您可以随机访问数组,但不能访问链表,并且不同的容器需要不同的遍历它们的方式。
根据容器的不同,它们可能是专门的模板,对吗?
Answers:
您已经得到答案,指出为什么所有迭代器都不必从单个Iterator基类继承。不过,我还有很多。C ++的目标之一是以零运行时间成本进行抽象。
如果所有迭代器都从一个通用基类继承而来,并使用基类中的虚函数定义接口,并且派生类提供了这些虚函数的实现,则可能(并且经常会)增加大量运行时间所涉及操作的开销。
让我们考虑一个简单的迭代器层次结构,该层次结构确实使用继承和虚函数:
template <class T>
class iterator_base {
public:
virtual T &operator*() = 0;
virtual iterator_base &operator++() = 0;
virtual bool operator==(iterator_base const &other) { return pos == other.pos; }
virtual bool operator!=(iterator_base const &other) { return pos != other.pos; }
iterator_base(T *pos) : pos(pos) {}
protected:
T *pos;
};
template <class T>
class array_iterator : public iterator_base<T> {
public:
virtual T &operator*() override { return *pos; }
virtual array_iterator &operator++() override { ++pos; return *this; }
array_iterator(T *pos) : iterator_base(pos) {}
};
然后让我们对其进行快速测试:
int main() {
char input[] = "asdfasdfasdfasdfasdfasdfasdfadsfasdqwerqwerqwerqrwertytyuiyuoiiuoThis is a stringy to search for something";
using namespace std::chrono;
auto start1 = high_resolution_clock::now();
auto pos = std::find(std::begin(input), std::end(input), 'g');
auto stop1 = high_resolution_clock::now();
std::cout << *++pos << "\n";
auto start2 = high_resolution_clock::now();
auto pos2 = std::find(array_iterator(input), array_iterator(input+sizeof(input)), 'g');
auto stop2 = high_resolution_clock::now();
std::cout << *++pos2 << "\n";
std::cout << "time1: " << duration_cast<nanoseconds>(stop1 - start1).count() << "ns\n";
std::cout << "time2: " << duration_cast<nanoseconds>(stop2 - start2).count() << "ns\n";
}
[注意:根据您的编译器,您可能需要做更多的事情,例如定义iterator_category,difference_type,reference等,以便编译器接受迭代器。
输出为:
y
y
time1: 1833ns
time2: 2933ns
[当然,如果您运行代码,则结果将与这些结果不完全匹配。]
因此,即使对于这种简单的情况(仅进行约80次增量和比较),我们也为简单的线性搜索增加了约60%的开销。特别是当迭代器最初添加到C ++中时,相当多的人根本不会接受具有那么大开销的设计。它们可能不会被标准化,即使有,也几乎没人会使用它们。
区别是什么和行为如何之间。
许多语言试图将两者融合在一起,但是它们是截然不同的东西。
如果是什么以及什么是...
如果一切都从中继承,object
那么将产生一些好处,例如:对象的任何变量都可以拥有任何值。但这也是磨擦,所有事物都必须表现得(方式如何)object
和外观(表现为)object
。
但:
object
由于对象在所有可能的实例之间都没有提供通用性,因此任何一种类型实际上都变得无用。否则,将存在一些对某些假定的通用属性具有残破/ object
horn 角/荒谬定义的对象,除了许多陷阱外,这些假定还证明了几乎通用的行为。
如果什么与方法不相关
或者你可以保持什么和如何独立。然后几个不同的类型(有什么共同之处的是什么)都可以表现得同样的方式从合作者看到了如何。在这个意义上的想法Iterator
是不是一个具体的东西,而是一个如何。具体怎么做你互动于一件事的时候,你还不知道什么你与互动。
Java(及类似)允许通过使用接口来实现此目的。在这方面,接口描述了通信方式,并暗含了遵循的通信协议和操作协议。声明自己属于给定How的任何What都声明它支持协议概述的相关通信和操作。这允许任何合作者依靠如何和不是陷入正好指定哪些什么的都可以使用。
C ++(和类似的)允许通过鸭式输入来实现。模板不在乎协作类型是否声明它遵循某种行为,而只是在给定的编译上下文中声明该对象可以以特定方式进行交互。这允许C ++指针和覆盖特定操作符的对象由同一代码使用。因为它们符合检查清单就被认为是等效的。
底层类型甚至不必迭代容器,它可以是任何what。另外,它允许某些协作者更加通用,想象一个函数只需要a++
一个迭代器就可以满足,指针,整数也可以,任何对象都可以实现operator++
。
规格过高
两种方法的问题都超出规范。
使用接口要求对象声明它支持给定的行为,这也意味着创建者必须从一开始就对它进行赋值。这会导致某些What 's未能切入,因为他们没有声明。这也意味着,以往什么有一个共同的祖先,接口代表如何。这确实可以回溯到的最初问题object
。这导致协作者过度指定了他们的要求,同时由于缺乏预期的行为而导致某些对象由于缺少声明而无法使用或成为隐藏的陷阱。
使用模板要求合作者使用完全未知的What,并且通过其交互来定义How。在某种程度上,这使得编写合作者更难,因为它必须分析什么它的通信原语(功能/场/等),同时避免编译错误,或至少点出如何在给定什么不符合其需求如何。这使得合作者需要从任何给定的绝对最小值是什么,让最广泛的是什么。不幸的是,这样做的缺点是允许对象的荒谬使用,这些对象为给定的技术提供通信原语如何但不遵循隐含的协议,允许发生各种不良情况。
迭代器
在这种情况下,Iterator
是如何它是互动的描述简写。根据定义,任何与该描述匹配的内容都是Iterator
。了解如何允许我们编写一般的算法,并有'短名单如何 S‘给出了具体的是什么 ’,要以使算法的工作提供必要。这份名单是功能/性能/等,它们的实现考虑到具体什么正在由算法处理。
因为C ++ 不需要具有(抽象)基类即可进行多态。它具有结构子类型化和命名子类型化。
在迭代器的特定情况下,令人困惑的是,先前的标准定义std::iterator
为(大约)
template <class Category, class T, class Distance = std::ptrdiff_t, class Pointer = T*, class Reference = T&>
struct iterator {
using iterator_category = Category;
using value_type = T;
using difference_type = Distance;
using pointer = Pointer;
using reference = Reference;
}
即,仅作为所需成员类型的提供者。它没有任何运行时行为,并且在C ++ 17中已弃用
请注意,即使这也不是一个共同的基础,因为类模板不是类,所以每个实例都独立于其他实例。