使自定义容器可迭代
Posted
技术标签:
【中文标题】使自定义容器可迭代【英文标题】:Making custom containers iterable 【发布时间】:2014-12-11 19:59:50 【问题描述】:在定义我的自定义容器时,只需添加 size()
和 []
就可以更简单且非常通用(对我而言)使它们可迭代。也就是说,具有以下成员方法:
unsigned int size() const . . .
T & operator[] (unsigned int pos) . . .
为了从 STL 算法中受益,我提供了一个适配器 从具有上述方法的 any 容器类到对 STL 函数有效的迭代器。 有了它,我可以轻松写出类似的东西
MyContainer<int, 5> mc;
IteratorFromContainer<MyContainer<int,5>, int> iter (&mc);
int i=1; for (auto & e : iter) e = i++;
for (auto e=iter.begin(); e!=iter.end(); ++e) cout << (*e) << endl;
int sum = std::accumulate(iter.begin(), iter.end(), 0);
int prod = std::accumulate(iter.begin(), iter.end(), 1, [](int a, int b) return a*b;);
令人惊讶的是(对我来说)我的适配器(模板)类(上面的示例代码)同样有效 具有以下任何一项(1、2 或 3):
template<typename Container, typename T>
// 1.
class IteratorFromContainer : public std::iterator<input_iterator_tag, T>
// 2.
class IteratorFromContainer : public std::iterator<output_iterator_tag, T>
// 3.
class IteratorFromContainer
为什么?适配器不应该从std::iterator
always 派生吗?
我应该使用什么样的迭代器(什么_tag),考虑到迭代器是基于随机访问(size()和[])并且具有输出和输入能力:RandomAccess, ContinguousIterator
?
谢谢
【问题讨论】:
非常相关但不确定是否重复:How to implement an STL-style iterator and avoid common pitfalls? 请注意,在您的示例中,IteratorFromContainer
似乎根本没有尝试成为迭代器。它是一个适配器类,本身是可迭代的。 iter.begin()
的类型应该是一个迭代器。什么类型的迭代器取决于该类型的接口,但我推测它很容易使其随机访问,这将使用std::random_access_iterator_tag
。
另外,如果你很狡猾,你不需要别人把T
传给IteratorFromContainer
,你可以从decltype(mc[0])
推导出来。
IteratorFromContainer
需要实现给定迭代器标签对应的功能(如std::output_iterator_tag
)。 iterator
基类只提供一些类型定义。如果它没有,当使用它的代码没有使用它承诺但没有的功能时,迭代器可能仍然工作。 C++17 中的概念可能会让编译器在这种情况下产生错误。
@cibercitizen1:这……很奇怪,我建议不要这样做。最好有一个除了创建迭代器之外什么都不做的给定线适配器类。
【参考方案1】:
C++ 使用了一种叫做鸭式打字的东西,它有很棒的优点,也有一些非常奇怪的缺点。其中一个缺点是,如果你违反了接口契约,它可能会在很久以后才会注意到,如果有的话。当您使用 for 循环时,编译器会使用迭代器的复制构造函数 operator++()
、operator==(...)
和 operator*()
,这些都是您拥有的,所以它有时可能会正常工作(我很确定 GCC 会指出错误在您的选项#2 和#3 中)。我不知道std::accumulate
的确切要求,但很可能它们是相似的。只要你有这些,你的代码就可以“工作”。
但是,使用选项#2 和#3,你打破了关于作为迭代器意味着什么的“合同”。因此,编译器或库可能会收到更新,您的代码可能会停止工作。每个迭代器类型的要求取决于您正在建模的迭代器的类型,有关更多详细信息,请参阅此页面:How to implement an STL-style iterator and avoid common pitfalls?。但是,最低要求是您必须具备以下操作:
iterator(const iterator&);
~iterator();
iterator& operator=(const iterator&);
iterator& operator++(); //prefix increment
reference operator*() const;
还有std::iterator_traits<IteratorFromContainer>
必须有这些类型定义:
typedef ???? difference_type; //almost always ptrdif_t
typedef ???? value_type; //almost always T
typedef ???? reference; //almost always T& or const T&
typedef ???? pointer; //almost always T* or const T*
typedef ???? iterator_category; //usually std::forward_iterator_tag or similar
为简单起见,如果您自己没有专门化std::iterator_traits
,它会检查您的迭代器是否具有这些类型定义,如果是,它将从您的迭代器中复制这些类型定义。 std::iterator
具有这些 typedef,因此如果您从它继承,您将自动拥有 typedef,因此 std::iterator_traits
将自动拥有它们,因此您将满足合同保证。但是继承std::iterator
不是必须的,你可以自己添加typedef,或者专门化iterator_traits
。
由于您使用的是operator[]
,因此使用std::random_access_iterator_tag
是有意义的,它的要求比上面列出的要多得多。同样,请参阅上面链接的我的其他答案,以了解有关这些成员究竟是什么的详细信息。
【讨论】:
以上是关于使自定义容器可迭代的主要内容,如果未能解决你的问题,请参考以下文章