Lambdas和std :: function,Modern C ++

问题描述 投票:4回答:2

我有一个类对数据应用一些计算。在这种情况下变换。然而,类不关心转换计算是什么或它需要的参数(它只知道它必须在整数向量上完成)。 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()。

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

c++ algorithm lambda functional-programming class-design
2个回答
1
投票

我不认为这是个好主意。我在这里看到的主要问题是使用std::function几乎肯定会阻止内联,这对于std :: algorithm性能非常重要。比较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);
}

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*用于额外的参数传递它。你必须要小心,并确保你传递正确类型的额外参数包。

© www.soinside.com 2019 - 2024. All rights reserved.