STL for_each具有多个返回值和/或虚拟基类仿函数

时间:2011-09-11 17:56:56

标签: c++ stl virtual functor

我尝试在代码中转换一些循环以使用STL的for_each功能。目前,我在同一组数据上计算和累加两个单独的值,要求我循环数据两次。为了速度,我想循环一次并积累两个值。建议使用for_each,因为它显然可以很容易地用于多线程或多处理器实现(我还没有学会如何做到这一点。)

创建一个只循环数据并计算两个值的函数很容易,但我需要返回两者。要与for_each一起使用,我需要在每次迭代时返回两个计算值,以便STL可以对它们求和。根据我的理解,这是不可能的,因为for_each期望返回一个值。

除了更清晰的代码(可以说是?)之外,使用for_each的目标是最终转移到多线程或多处理器实现,以便可以并行完成数据循环,从而使事情运行得更快。

有人建议我使用仿函数而不是函数。但是,这引发了两个问题。

  1. 如何使用仿函数代替两个值的返回累积?
  2. 我有两种应用此算法的方法。当前代码有一个虚拟基类,然后是两个继承和实现实际工作代码的类。我无法弄清楚如何使用“虚拟仿函数”,以便每个方法类都可以实现自己的版本。
  3. 谢谢!

2 个答案:

答案 0 :(得分:6)

以下是使用仿函数并行执行两次累加的示例。

struct MyFunctor
{
    // Initialise accumulators to zero
    MyFunctor() : acc_A(0), acc_B(0) {}

    // for_each calls operator() for each container element
    void operator() (const T &x)
    {
        acc_A += x.foo();
        acc_B += x.bar();
    }

    int acc_A;
    int acc_B;
};


// Invoke for_each, and capture the result
MyFunctor func = std::for_each(container.begin(), container.end(), MyFunctor());

[请注意,您还可以考虑使用std::accumulate(),并为operator+添加适当的重载。]

对于虚拟仿函数,你不能直接这样做,因为STL函数按值而不是通过引用来获取仿函数(因此你会遇到切片问题)。你需要实现一种“代理”仿函数,而这种仿函数又包含对你的虚拟函子的引用。 * 类似于:

struct AbstractFunctor
{
    virtual void operator() (const T &x) = 0;
};

struct MyFunctor : AbstractFunctor
{
    virtual void operator() (const T &x) { ... }
};

struct Proxy
{
    Proxy(AbstractFunctor &f) : f(f) {}
    void operator() (const T &x) { f(x); }
    AbstractFunctor &f;
};

MyFunctor func;
std::for_each(container.begin(), container.end(), Proxy(func));

* Scott Meyers在他出色的Effective STL的第38项中给出了一个很好的例子。

答案 1 :(得分:4)

三个(主要)方法

好的,我最终做了三个(主要)实现(有很小的变化)。我做了一个简单的基准测试,看看是否有任何效率差异。查看底部的基准测试部分

1。带有c ++ 0x lambda

std::for_each

采用一些c ++ 0x快捷方式:请参阅http://ideone.com/TvJZd

#include <vector>
#include <algorithm>
#include <iostream>

int main()
{
    std::vector<int> a = { 1,2,3,4,5,6,7 };

    int sum=0, product=1;

    std::for_each(a.begin(), a.end(), [&] (int i) { sum+=i; product*=i; });

    std::cout << "sum: " << sum << ", product: " << product << std::endl;

    return 0;
}

打印

sum: 28, product: 5040

正如其他人所说,你通常更喜欢正常的循环:

for (int i: a)
{ sum+=i; product*=i; }

这两者都是

  • 更短,
  • 更清晰,
  • 不太意外(ref capture)和
  • 可能会被编译器更优化

另外,在非c ++ 11 / 0x中非常接近:

for (std::vector<int>::const_iterator it=a.begin(); it!=a.end(); ++it)
{ sum+=*it; product*=*it; }

2。带有手写累加器对象的std::accumulate

根据std::accumulate添加了一个:请参阅http://ideone.com/gfi2C

struct accu_t
{
    int sum, product;
    static accu_t& handle(accu_t& a, int i)
    {
        a.sum+=i;
        a.product*=i;
        return a;
    }
} accum = { 0, 1 };

accum = std::accumulate(a.begin(), a.end(), accum, &accu_t::handle);

3。 std::accumulate std::tuple

好的我无法抗拒。这是一个accumulate,但在std::tuple上运行(不需要仿函数类型):请参阅http://ideone.com/zHbUh

template <typename Tuple, typename T>
    Tuple handle(Tuple t, T v)
{
    std::get<0>(t) += v;
    std::get<1>(t) *= v;
    return t;
}

int main()
{
    std::vector<int> a = { 1,2,3,4,5,6,7 };

    for (auto i=1ul << 31; i;)
    {
        auto accum = std::make_tuple(0,1);
        accum = std::accumulate(a.begin(), a.end(), accum, handle<decltype(accum), int>);

        if (!--i)
            std::cout << "sum: " << std::get<0>(accum) << ", product: " << std::get<1>(accum) << std::endl;
    }

    return 0;
}

基准:

通过累积2 <&lt;&lt; 31次测量(参见基于std :: tuple的变体的片段)。仅使用-O2和-O3进行测试:

  • 所示的任何方法(0.760s)之间没有可测量的差异:

  • 所有变体都表现出从-O2到-O3(13.8s到0.760s)的加速度超过18x ,无论选择的实施方式如何

  • tuple / accumulate表现与Tuple& handle(Tuple& t, T v)(通过引用)完全相同。