Lambdas和std :: function,现代C ++

时间:2019-03-28 22:03:41

标签: c++ algorithm lambda functional-programming class-design

我有一个对数据进行一些计算的类。在这种情况下,将进行转换。但是,该类不在乎转换计算是什么或它需要的参数(它只知道必须在整数向量上完成)。 compute()成员函数进行所有计算。

class Transform {
  public:
    Transform();
    void compute();
    void print();
  private:
    std::vector<int> data;
};

在较旧的C ++中,我猜想应该做的正确的事情是从Transform类继承(并使计算虚拟化),并使用其特定参数实现所有自定义计算功能并重载它们。

class TransformType1 : public Transform {
  public:
    Transform();
    void compute();
    void compute(int max_int, int min_int); //Truncates all the values outside of range
    void print();
  private:
    std::vector<int> data;
};

使用lambda和给定向量的std::transform可以很好地做到这一点,那么,为什么不在类内部使用相同的方法呢?

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

// Generic class, used for other stuff does not care about the specifics
// of the parameters used in the compute lambda

class Transform {
 public:
  Transform();
  void compute();
  // This std::function will be set though the constructor in the future and
  // will be private
  std::function<int(int)> t_lambda;
  void print();

 private:
  std::vector<int> data;
};

Transform::Transform() : data({1, 3, 4, 5, 2, 1, 4, 5, 3, 5, 3, 6, 4, 6, 12}) {}

// Apply the transform given the function object
void Transform::compute() {
  std::transform(data.begin(), data.end(), data.begin(), t_lambda);
}

void Transform::print() {
  for (auto& e : data) {
    std::cout << e << "  ";
  }
  std::cout << std::endl;
}

int main() {
  Transform f;
  int max_num = 2; // External parameters 

  // The important part: lambda parameters are taken with the capture feature
  f.t_lambda = std::function<int(int)>([max_num](int x) -> int {
    // Truncate values above a threshold
    return x > max_num ? max_num : x;
  });

  f.compute();
  f.print();
}

现在,我可以简单地在类外部定义计算函数规则(按预期方式),并且可以保持相同的类型(无继承模式),甚至可以向lambda函数添加更多参数(而不是常量),而无需重载compute()

过去我没有看过很多这样的代码,所以我不确定这是否是一个好主意。在这种情况下,我还可以使用其他哪些模式?

2 个答案:

答案 0 :(得分:1)

我认为这不是一个好主意。我在这里看到的主要问题是,std::function的使用几乎肯定会阻止内联,这对于std ::算法的性能来说是非常重要的。将your approachthis进行比较。第一个示例基本上是上面代码的精简版本。在第二个版本中,我直接将lambda传递给Transform::compute,而不是通过std::function。如果查看生成的代码,您将看到在第二个版本中,编译器基本上可以内联所有内容。另一方面,在第一个版本中,编译器无法内联任何内容。计算函数的每次迭代都将间接调用std::function中包含的lambda。

除了这些性能问题外,我不清楚此类Transform类到底代表什么。它将输入数据容器与std::functionprint()方法捆绑在一起,这给我带来了不相关功能的相当随意的聚集。从概念上讲,我认为转换可以应用于某些输入数据以生成某些输出数据。应用于变换的数据似乎与变换本身无关。将向量打印到std::cout的行为更加如此。在类中封装某种类型的转换没有错。但是我要说的是,您然后希望该Transform类的实例表示一个特定的转换,然后可以将其应用于任何给定的输入数据。 For example

#include <algorithm>
#include <vector>

class MyTransform
{
    int max_num;

public:
    MyTransform(int max_num = 2) : max_num(max_num) {}

    void operator ()(std::vector<int>& data) const
    {
        std::transform(std::begin(data), std::end(data), std::begin(data), [this](int x)
        {
            return x > max_num ? max_num : x;
        });
    }
};

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

  MyTransform f(2);

  f(data);
}

在这里,MyTransform实际上代表示例转换的一个实例(对于某些参数max_num)。 MyTransform个对象可以传递并应用于任何给定的std::vector<int>。或the shorthand version

#include <algorithm>
#include <vector>

auto my_transform(int max_num)
{
    return [max_num](auto&& data)
    {
        std::transform(std::begin(data), std::end(data), std::begin(data), [max_num](int x)
        {
            return x > max_num ? max_num : x;
        });
    };
}

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

  auto f = my_transform(2);

  f(data);
}

答案 1 :(得分:0)

从有效性和简便性的角度来看,是的-这是一种做事并清除许多原本必须编写的多态代码的完美有效方法(正如您在C ++的较早版本中所提到的)。它非常安全,用途广泛,并且具有广泛的标准化界面。

但是,从性能的角度来看,std::function被认为是不好的,因为它是一个多态适配器,并且具有与此相关的所有开销。因此,如果要最大速度,则需要使用常规的,名称空间级别或静态的成员函数。在这种情况下,您可以考虑使用void*参数包习惯用法。

但是请记住,只有在您的程序已经运行并且只想对其速度进行优化时,这才可以实现:

// holds extra args for do_something_1()
struct pack_1
{
    int    something;
    double something_else;
};
// holds extra args for do_something_2()
struct pack_2
{
    float       cool_stuff;
    std::string another_extra_param;
};

// expects _extra to be of type pack_1
void do_something_1(int a, int b, void *_extra)
{
    pack_1 *extra = (pack_1*)_extra;
    ...
}
// expects _extra to be of type pack_2
void do_something_2(int a, int b, void *_extra)
{
    pack_2 *extra = (pack_2*)_extra;
    ...
}

然后,您可以使用纯指针:void (*)(int, int, void*)用于使用的函数,void*用于传递的额外参数。您只需要小心并确保将正确的附加参数包类型传递给它即可。