【问题标题】:C++ "periodic" iterator over the custom container自定义容器上的 C++“定期”迭代器
【发布时间】:2020-12-08 01:33:07
【问题描述】:

我有一个包含特定类元素向量的类。主要思想是根据序列的一个周期(elems_)和周期数(nperiod_)生成元素的周期序列,所以我不需要存储所有元素,只需要一个周期.

class PeriodicContainer
{
private:
  std::vector<Class> elems_; // elements
  size_t nperiod_; // period of repetition of elems_
public:
  PeriodicContainer();
  PeriodicContainer(const std::vector<Class>& elems, size_t nperiod);
  /*...*/
}

是否可以为PeriodicContainer 实现自定义迭代器,以便我可以执行(半伪代码)之类的操作:

PeriodicContainer container({Class(1), Class(2)}, 4);
for (auto it : container)
  std::cout << it << '\n';

输出将是

Class(1)
Class(2)
Class(1)
Class(2)
Class(1)
Class(2)
Class(1)
Class(2)

【问题讨论】:

  • 你可以调整任何 RandomAccessIterator 使其表现得像这样。只需检查您到达终点的++ 运算符,然后在这种情况下返回起点。
  • 这能回答你的问题吗? Is there a standard cyclic iterator in C++

标签: c++ stl iterator containers


【解决方案1】:

如果你的底层容器只是一个std::vector,那么你就知道它是一个连续的容器——这实际上让这很容易。

你可以从以下组成一个迭代器:

  • 指向被迭代容器的指针(或引用),并且
  • 当前迭代计数(注意:不是“索引”)。在环绕容器的 size() 之后,这将用作底层容器的 operator[] 的“索引”。

这个迭代器的行为很简单:

  • 每次增量只会增加当前计数
  • 每次取消引用都返回(*elems_)[current_ % elems_-&gt;size()],这将说明“周期”的循环。
  • begin() 将简单地返回一个带有 0 计数的迭代器,并且
  • end() 将返回一个计数为elems_.size() * nperiod_ 的迭代器

LegacyForwardIterator 的示例如下:


template <typename T>
class PeriodicContainerIterator
{
public:
    using value_type = T;
    using reference = T&;
    using pointer = T*;
    using difference_type = std::ptrdiff_t;
    using iterator_category = std::forward_iterator_tag;
 
    PeriodicContainerIterator(std::vector<T>* elems, std::size_t current)
        : elems_{elems},
          current_{current}
    {}

    reference operator*() {
        return (*elems_)[current_ % elems_->size()]
    }
    pointer operator->() {
        return &(*elems_)[current_ % elems_->size()];
    }
    PeriodicContainerIterator& operator++() const {
        ++current_;
        return (*this);
    }
    PeriodicContainerIterator operator++(int) const {
        auto copy = (*this);
        ++current_;
        return copy;
    }
 
    bool operator==(const PeriodicContainerIterator& other) const {
        return current_ == other.current_;
    }
    bool operator!=(const PeriodicContainerIterator& other) const {
        return current_ != other.current_;
    }
    
private:
    std::vector<T>* elems_;
    std::size_t current_;
};

然后容器会将begin()end() 定义为:


PeriodicContainerIterator<Class> begin() {
    return PeriodicContainerIterator<Class>{&elems_, 0};
}
PeriodicContainerIterator<Class> end() {
    return PeriodicContainerIterator<Class>{&elems_, elems_->size() * nperiod_};
}

您可以轻松地将其设置为LegacyRandomAccessIterator,但这需要大量额外的函数来增加这个答案。


如果您不是特别需要它作为 迭代器,而只是想要一种简单的方法来访问周期性序列中的每个元素,那么如果您要这样做可能会更容易阅读/理解进入类似for_each 的调用,该调用需要回调。例如:

template <typename Fn>
void forEach(Fn&& fn)
{
    for (auto i = 0; i < nperiod_; ++i) {
        for (auto& e : elems_) {
            fn(e);
        }
    }
}

允许使用如下:

container.forEach([&](auto& e){
    // 'e' is each visited element
});

【讨论】:

  • 您将如何为自定义容器而不是专门为std::vector 实现此功能? OP 专门将 custom container 放在标题中。基于 RandomAccessIterator 的实现将更有意义,该实现将适用于任何提供它的容器。附带说明一下,您应该在构造函数中使用std::vector&lt;T&gt;&amp;,然后将其转换为指针。您不想接受可为空的参数,那么为什么不使用引用呢?
  • 这个自定义容器的迭代器——它是根据PeriodicContainer的实现专门编写的,这就是所要求的。在其底层容器上工作的迭代器通常是紧密耦合的。至于参考与参数点:这是一个见仁见智的问题。指针并不意味着可以为空,它意味着在调用站点上更具可读性,这正在捕获对象的非拥有生命周期(例如 &amp;elems_ 而不是 elems_,这不那么容易阅读终身捕获)
  • 现在我了解您的方法了。以一种特定的方式实现一个像周期性迭代器这样通用的概念,以至于它只适用于单个类,这似乎是违反直觉的。但我想这让这个例子很简单。至于指针与引用约定,从技术上讲,指针始终可以为空,这总是会导致您是否可以传递nullptr 的问题。除此之外,这个论点确实是基于意见的,不值得讨论。
  • 多多多谢!我将明确地在我的代码中使用它。实际上,我想做这样的事情,但由于缺乏经验而决定在这里问。
【解决方案2】:

如果你可以使用 range-v3,你可以这样做:

namespace rv = ranges::views;    

std::vector<Class> Container { Class(1), Class(2) };

for (auto it : rv::repeat_n(Container, 4) | rv::join)
    std::cout << it;

而且不必自己编写任何额外的代码。这也适用于任何连续容器,而不仅仅是std::vector

这是demo

【讨论】:

    猜你喜欢
    • 2013-02-17
    • 2021-09-15
    • 2015-09-06
    • 2015-11-19
    • 1970-01-01
    • 1970-01-01
    • 2010-10-24
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多