使自定义容器可迭代

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&lt;IteratorFromContainer&gt; 必须有这些类型定义:

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 是有意义的,它的要求比上面列出的要多得多。同样,请参阅上面链接的我的其他答案,以了解有关这些成员究竟是什么的详细信息。

【讨论】:

以上是关于使自定义容器可迭代的主要内容,如果未能解决你的问题,请参考以下文章

如何使自定义 UIView 可访问?

使自定义 .NET 异常可序列化的正确方法是啥?

如何使用 configureWebpack 使自定义变量可用于组件?

使自定义单元格(使用自动布局创建)高度为零

Nativescript-vue:如何使自定义组件可点击?

如何使自定义部分可执行(.text 除外)