保证省略和链接函数调用

Bar*_*rry 18 c++ copy-elision c++17

假设我有以下类型:

struct X {
    X& operator+=(X const&);
    friend X operator+(X lhs, X const& rhs) {
        lhs += rhs;
        return lhs;
    }
};
Run Code Online (Sandbox Code Playgroud)

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

X sum = a + b + c + d;
Run Code Online (Sandbox Code Playgroud)

在C++ 17中,我对这个表达式执行的副本和移动有多少保证?那么无保障的省略呢?

How*_*ant 19

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

  1. 制作a要绑定的副本lhs.
  2. 将构造lhs移出第一个+.
  3. 第一个的返回+将使用elision 绑定到lhs第二个的by 参数+.
  4. 第二次返回lhs将导致第二次移动建设.
  5. 第三次返回lhs将导致第三次移动建设.
  6. 从第三个返回的临时+建筑将在sum.

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

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

X sum = std::move(a) + b + c + d;
Run Code Online (Sandbox Code Playgroud)

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

上述结果已经通过装备X复制和移动构造函数来确认.


更新

如果您对不同的优化方法感兴趣,可以从lhs开始重载X 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;
}
Run Code Online (Sandbox Code Playgroud)

这使事情减少到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;
}
Run Code Online (Sandbox Code Playgroud)

让你一举一动,一动.请注意,在此最新设计中,如果您的客户端执行此操作:

X&& x = a + b + c;
Run Code Online (Sandbox Code Playgroud)

然后x是一个悬空参考(这就是为什么std::string不这样做).

  • 我甚至认为忽视"lhs"的回归是合法的.但是我也不认为任何编译器编写者都有任何动机使它合法(无论如何都没有人尝试过). (2认同)

Nic*_*las 9

好的,让我们从这开始:

X operator+(X lhs, X const& rhs) {
    lhs += rhs;
    return lhs;
}
Run Code Online (Sandbox Code Playgroud)

这将始终激发从参数到返回值对象的复制/移动.C++ 17不会改变这一点,任何形式的省略都不能避免这种复制.

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

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

C++ 17意味着返回的prvalue a + b将用于直接初始化参数operator+.这不需要复制/移动.但是,将从该参数复制此返回值.所以这是1副本和2个移动/副本.

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


你的问题似乎真的是C++中的参数是否仍然被排除在elision之外17.因为它们已被排除在先前版本中.这不会改变; 从elision中排除参数的原因尚未失效.

"保证省略" 仅适用于prvalues.如果它有名称,则它不能是prvalue.