18

假设我有以下类型:

struct X {
    X& operator+=(X const&);
    friend X operator+(X lhs, X const& rhs) {
        lhs += rhs;
        return lhs;
    }
};

我有声明(假设所有命名变量都是左值类型X):

X sum = a + b + c + d;

在 C++17 中,我对这个表达式将执行多少个副本和移动有什么保证?非保证省略怎么办?

4

2 回答 2

19

这将执行 1 个复制构造和 3 个移动构造。

  1. 制作副本a以绑定到lhs.
  2. 将构造lhs移出第一个+.
  3. 第一个的返回+将通过省略绑定到lhs第二个的按值参数+
  4. 第二个的返回lhs将导致第二个移动构造。
  5. 第三步的回归lhs将引发第三步建设。
  6. 从第三个返回的临时+将在sum.

对于上述每个移动构造,存在可选地省略的另一个移动构造。所以你只保证有1个副本和6个动作。但在实践中,除非你 ,否则你-fno-elide-constructors将拥有 1 个副本和 3 个移动。

如果您在此表达式之后不引用a,则可以进一步优化:

X sum = std::move(a) + b + c + d;

导致 0 个副本和 4 次移动(7 次移动-fno-elide-constructors)。

上面的结果已经被一个X已检测的复制和移动构造函数证实。


更新

如果您对优化它的不同方法感兴趣,您可以从重载 lhs onX const&和开始X&&

friend X operator+(X&& lhs, X const& rhs) {
    lhs += rhs;
    return std::move(lhs);
}
friend X operator+(X const& lhs, X const& rhs) {
    auto temp = lhs;
    temp += rhs;
    return temp;
}

这将事情降低到 1 个副本和 2 个移动。如果您愿意限制您的客户永远+无法通过引用获得您的回报,那么您可以X&&从这样的重载之一中返回:

friend X&& operator+(X&& lhs, X const& rhs) {
    lhs += rhs;
    return std::move(lhs);
}
friend X operator+(X const& lhs, X const& rhs) {
    auto temp = lhs;
    temp += rhs;
    return temp;
}

让您减少 1 个副本和 1 个动作。请注意,在此最新设计中,如果您的客户曾经这样做:

X&& x = a + b + c;

thenx是一个悬空引用(这就是为什么std::string不这样做)。

于 2017-02-10T18:27:24.380 回答
9

好的,让我们从这个开始:

X operator+(X lhs, X const& rhs) {
    lhs += rhs;
    return lhs;
}

这将始终引发从参数到返回值对象的复制/移动。C++17 没有改变这一点,任何形式的省略都不能避免这个副本。

现在,让我们看一下您表达的一部分:a + b. 由于 的第一个参数operator+是按值取值的,a因此必须将其复制到其中。所以这是一个副本。返回值将被复制到返回纯右值中。所以这是 1 个副本和一个移动/副本。

现在,下一部分:(a + b) + c

C++17 表示从返回的纯右值a + b将用于直接初始化 的参数operator+。这不需要复制/移动。但是这个的返回值将从那个参数复制。所以这是 1 个副本和 2 个移动/副本。

对最后一个表达式重复此操作,即 1 个副本和 3 个移动/副本。sum将从纯右值表达式初始化,因此不需要在那里进行复制。


您的问题似乎真的是参数是否仍然被排除在 C++17 中的省略之外。因为它们已经在以前的版本中被排除了。这不会改变;从省略中排除参数的原因尚未失效。

“保证省略”仅适用于纯右值。如果它有名称,则它不能是纯右值。

于 2017-02-10T18:22:47.917 回答