使用move语义优化二进制算术运算

optimizing binary arithmetic operations using move semantics

本文关键字:二进制 算术运算 优化 语义 move 使用      更新时间:2023-10-16

我正在用一个简单的Vector类尝试rvalue引用,试图消除二进制操作中不必要的临时性。经过一点挣扎,我发现operator+()有以下两个过载:

// overload called if right = lvalue and left = lvalue/rvalue
friend Vector<T> operator+(Vector<T> a, const Vector<T>& b) {
  a += b;
  return a;
}
// overload called if right = rvalue and left = lvalue/rvalue
friend Vector<T> operator+(const Vector<T>& a, Vector<T>&& b) {
  b += a;
  return std::move(b);
}

我可以确保,在像auto x = a+b+c+d+...;这样的表达式中,只要至少ab是临时的,就会在不创建任何新临时的情况下调用move构造函数。

另一方面,即使ab之后的值之一(比如d)是lvalue,从技术上讲,这应该足以避免复制。编译器是否可以通过扫描给定的表达式来找到至少一个临时表达式并根据该值开始调用operator+来进行任何优化?

示例1:

#include "vector.h"
Vector<double> get() {
  return {0, 1, 2, 6};
}
int main (){
  auto a = get();
  auto b = get();
  auto c = a + get() + b;
  std::cout << c << std::endl;
  return 0;
}

输出:

calling move ctor
calling move ctor
[0, 3, 6, 18]

示例2:

#include "vector.h"
Vector<double> get() {
  return {0, 1, 2, 6};
}
int main (){
  auto a = get();
  auto b = get();
  auto c = a + b + get();
  std::cout << c << std::endl;
  return 0;
}

输出:

calling copy ctor
calling move ctor
[0, 3, 6, 18]

我想答案是否定的。在您的代码中

auto c = a + b + get();

编译器必须首先对a和b调用运算符+()。我认为这个执行顺序是在语言规范中预定义的。编译器不应该首先执行b+get(),因为不同的执行顺序可能会导致(a+b+get)的不同返回值,以及不同的副作用。因此,处决的顺序应该保持不变。

编译器首先可以对+b部分做些什么吗?由于a和b都是l值,编译器必须选择一个接受两个l值自变量的函数。并不是说编译器开发人员不能添加新的优化,而是编译器只能按照语言中指定的方式进行。假设编译器将函数的r值版本用于a+b。然后,在函数中,a或b将被修改(因为它们是r值),这不是有意的:为什么我们需要修改操作数来获得它们的和?

如果您愿意在函数中修改a和b,只需在调用函数之前将它们强制转换为r值,如std::move(a) + std::move(b) + get()

希望这能有所帮助。