【问题标题】:Equivalent of enumerators in C++11?C ++ 11中的枚举器等价物?
【发布时间】:2015-07-03 02:37:37
【问题描述】:

在 C# 中,您可以非常简单地定义自定义枚举,例如:

public IEnumerable<Foo> GetNestedFoos()
{
    foreach (var child in _SomeCollection)
    {
        foreach (var foo in child.FooCollection)
        {
            yield return foo;
        }
        foreach (var bar in child.BarCollection)
        {
            foreach (var foo in bar.MoreFoos)
            {
                yield return foo;
            }
        }
    }
    foreach (var baz in _SomeOtherCollection)
    {
        foreach (var foo in baz.GetNestedFoos())
        {
            yield return foo;
        }
    }
}

(这可以使用 LINQ 和更好的封装来简化,但这不是问题的重点。)


在 C++11 中,您可以进行类似的枚举,但 AFAIK 它需要访问者模式:

template<typename Action>
void VisitAllFoos(const Action& action)
{
    for (auto& child : m_SomeCollection)
    {
        for (auto& foo : child.FooCollection)
        {
            action(foo);
        }
        for (auto& bar : child.BarCollection)
        {
            for (auto& foo : bar.MoreFoos)
            {
                action(foo);
            }
        }
    }
    for (auto& baz : m_SomeOtherCollection)
    {
        baz.VisitAllFoos(action);
    }
}

有没有办法做一些更像第一种的方法,函数返回一个可以在外部迭代而不是在内部调用访问者的范围?

(我的意思不是构造一个std::vector&lt;Foo&gt; 并返回它——它应该是一个就地枚举。)

我知道 Boost.Range 库,我怀疑它会参与解决方案,但我不是特别熟悉它。

我也知道可以定义自定义迭代器来做这种事情(我也怀疑这可能会涉及到答案)但我正在寻找易于编写的东西,理想情况下不比此处显示的示例和可组合的(例如 _SomeOtherCollection)。

我更喜欢不需要调用者使用 lambdas 或其他仿函数的东西(因为这只会让它再次成为访问者),尽管我不介意在需要时在内部使用 lambdas(但仍然希望在那里避免它们也)。

【问题讨论】:

    标签: c++11 for-loop boost boost-range


    【解决方案1】:

    如果我正确理解您的问题,您希望对集合的所有元素执行一些操作。

    C++ 有一组广泛的迭代器操作,在iterator header 中定义。大多数集合结构,包括您引用的std::vector,都有.begin.end 方法,它们不带参数并将迭代器返回到结构的开头和结尾。这些迭代器有一些可以手动执行的操作,但它们的主要用途是algorithm header,它定义了几个非常有用的迭代函数。

    在您的具体情况下,我相信您需要 for_each 函数,它需要一个范围(作为从头到尾的迭代器)和一个要应用的函数。因此,如果您有一个名为 action 的函数(或函数对象),并且您想将其应用于名为 data 的向量,则以下代码将是正确的(假设所有必要的标头都已正确包含):

    std::for_each(data.begin(), data.end(), action);
    

    请注意,for_each 只是算法头提供的众多功能之一。它还提供了搜索集合、复制数据集、排序列表、查找最小值/最大值等等的功能,所有这些都可以泛化到任何具有迭代器的结构上。如果这些还不够,您可以通过阅读迭代器支持的操作来编写自己的。只需定义一个模板函数,该函数接受不同类型的迭代器并记录您想要的迭代器类型。

    template <typename BidirectionalIterator>
    void function(BidirectionalIterator begin, BidirectionalIterator end) {
        // Do something
    }
    

    最后一点是,到目前为止提到的所有操作也可以在数组上正确操作,前提是您知道数组的大小。不用写.begin.end,而是写+ 0+ n,其中n 是数组的大小。为了将数组的类型衰减为指针以使其成为有效的迭代器,通常需要简单的零加法,但数组指针确实是随机访问迭代器,就像任何其他容器迭代器一样。

    【讨论】:

    • 我说我对访问者模式不感兴趣。
    【解决方案2】:

    您可以做的是编写自己的适配器函数,并使用相同类型的不同元素范围调用它。

    这是一个未经测试的解决方案,可能需要一些调整才能使其编译,但它会给你一个想法。它使用可变参数模板从一个集合移动到下一个集合。

    template<typename Iterator, Args...>
    visitAllFoos(std::pair<Iterator, Iterator> collection, Args&&... args)
    {
      std::for_each(collection.first, collection.second, {}(){ // apply action });
      return visitAllFoos(std::forward<Args>(args)...);
    }
    
    //you can call it with a sequence of begin/end iterators
    visitAllFoos(std::make_pair(c1.begin(), c1,end()), std::make_pair(c2.begin(), c2,end()))
    

    【讨论】:

    【解决方案3】:

    我相信,你想要做的事情可以用 Boost.Range 来完成,尤其是 joinany_range(如果你想隐藏容器的类型并删除 @ 则需要后者987654324@从界面)。

    但是,由此产生的解决方案在复杂性和性能方面都不是很实用——主要是因为嵌套的joined_ranges 和any_range 产生的类型擦除开销。就个人而言,我只会构造std::vector&lt;Foo*&gt; 或使用访问。

    【讨论】:

    • 使用访问来构建std::vector&lt;Foo*&gt; 是我目前的做法,但正如我所说,我对此并不特别满意。但感谢您确认。
    【解决方案4】:

    【讨论】:

    • 抱歉回复晚了。这是一个可爱的想法,但在这种情况下使用无堆栈协程需要将基于范围的 for 解包回迭代器(因为不保留局部值——任何需要在 yield 之间记住的东西都必须是成员变量而不是局部变量),这需要大量分解代码(尽管不像没有协程那样糟糕)。使用堆栈式协程可以避免这个问题,但对于这种情况来说,这感觉太重了。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-03-22
    • 1970-01-01
    • 2019-01-18
    • 2011-03-15
    • 1970-01-01
    • 2010-10-09
    • 1970-01-01
    相关资源
    最近更新 更多