高效的函数式(C++11可以)

Efficient functional style (C++11 ok)

本文关键字:C++11 可以 函数 高效      更新时间:2023-10-16

假设我有以下内容:

struct point
{
  double x;
  double y;
  double z;
};

我可以写以下内容:

void point_mult(point& p, double c) { p.x *= c; p.y *= c; p.z *= c; }
void point_add(point& p, const point& p2) { p.x += p2.x; p.y += p2.y; p.z += p2.z; }

所以我可以做以下事情:

point p{1,2,3};
point_mult(p, 2);
point_add(p, point{4,5,6});

这不需要point的副本,只需要两个构造,即point{1,2,3}的构造和point{4,5,6}的构造。我相信这适用于即使point_addpoint_multpoint{...}在单独的编译单元中(即不能内联)。

然而,我想用一种更实用的风格来编写代码,比如:

point p = point_add(point_mult(point{1,2,3}, 2), point{4,5,6});

我如何编写point_multpoint_add,从而不需要副本(即使point_multpoint_add在单独的编译单元中),或者函数式风格在C++中被迫没有那么高效?

让我们忽略这个问题隐含的谬误(即自动复制意味着效率降低)。我们也可以忽略这样一个问题,即是否真的会发生任何复制,或者是否所有复制都会被任何半吊子的编译器所消除。让我们从表面上看:这能在不复制的情况下完成吗?

是的,它可能是r值引用的唯一其他合法使用(尽管之前被忽视的规定使这个用例变得可疑):

point &&point_mult(point &&p, double c);

当然,这只适用于临时措施。所以你需要一个l值的替代版本:

point &point_mult(point &p, double c);

关键是,您可以按原样传递引用,既可以作为对临时值的引用,也可以作为对l值的引用。

这可以用非常难看的模板元编程来完成。例如,eigen使用模板,这样像matrix1 + matrix2 * matrix3这样的表达式就不需要创建任何临时性。其工作原理是矩阵的+*运算符不返回Matrix对象,而是返回某种基于表达式参数类型模板化的矩阵表达式对象。然后,这个矩阵表达式对象只能在需要时计算表达式的部分,而不是创建临时对象来存储子表达式的结果。

实际上,实现这一点可能会变得相当混乱。如果你感兴趣的话,可以看看艾根的来源。Boost的uBlas也做了类似的事情,尽管它没有本征那么广泛。

一种有效(通用)的技术是表达式模板。你可以在这里阅读一个很好的介绍性解释。

它很难实现,而且基于模板,不能使用单独的编译单元,但它非常高效。符号计算中一个有趣的应用是解析:BoostSpirit用它们构建了非常高效的解析器。

C++11auto关键字有助于在实际编程任务中使用,与处理复杂类型时一样,请参阅其他答案。

首先,为什么不使用"更好"的函数?

struct Point {
  double x;
  double y;
  double z;
  Point& operator+=(Point const& right) {
    x += right.x; y += right.y; z += right.z;
    return *this;
  }
  Point& operator*=(double f) {
    x *= f; y *= f; z *= f;
    return *this;
  }
};

现在它可以用作:

Point p = ((Point{1,2,3} *= 2) += Point{4,5,6});

但我真的认为你太担心这里的副本(和性能)了。

  1. 让它发挥作用
  2. 快一点

如果你没有任何已经有效的东西,谈论性能就像追逐工厂。。。瓶颈很少在我们想象的地方。

point_mult()的定义更改为:

point& point_mult(point& p, double c) { p.x *= c; p.y *= c; p.z *= c; return p; }
^^^^^^                                                                ^^^^^^^^^

并称之为:

point & p = point_add(point_mult(*new point{1,2,3}, 2), point{4,5,6});
     ^^^                         ^^^^^

不存在任何复制。但是,您必须稍后执行delete &p;以释放内存。

相关文章: