保证省略和链式函数调用

Posted

技术标签:

【中文标题】保证省略和链式函数调用【英文标题】:Guaranteed elision and chained function calls 【发布时间】:2017-02-10 18:04:17 【问题描述】:

假设我有以下类型:

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 中,我对这个表达式将执行多少个副本和移动有什么保证?非保证省略呢?

【问题讨论】:

【参考方案1】:

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

    复制a 以绑定到lhs。 将构造 lhs 移出第一个 +。 第一个 + 的返回值将绑定到第二个 + 的按值 lhs 参数,并带有省略号。 第二个lhs 的返回将引发第二个移动构造。 第三个lhs 的返回将导致第三个移动构造。 从第三个+ 返回的临时值将在sum 构造。

对于上面描述的每个移动结构,都有另一个移动结构可以选择省略。所以你只有保证有 1 个副本和 6 个动作。但在实际操作中,除非你-fno-elide-constructors,否则你将有1个副本和3个动作。

如果你在这个表达式之后没有引用a,你可以进一步优化:

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

产生 0 个副本和 4 个移动(-fno-elide-constructors 的 7 个移动)。

上面的结果已经通过X 得到证实,该X 检测了复制和移动构造函数。


更新

如果您对优化此功能的不同方法感兴趣,可以从重载 X const&X&& 上的 lhs 开始:

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;

那么x 是一个悬空引用(这就是std::string 不这样做的原因)。

【讨论】:

所以没有省略链?例如将a+b的返回直接构造成operator+(??, c)? 我什至认为忽略lhs 的返回是不合法的。然而,我也不认为任何编译器作者有任何动机使其合法(反正没有人尝试)。 为什么它可能是非法的?以后能不能合法化? @Orient:我想说的是标准不允许这样做。据我所知,没有人提出过它。我怀疑其原因是没有人想出如何实施它。但这些都是猜测,我不是编译器专家。【参考方案2】:

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

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返回的prvalue会直接用来初始化operator+的参数。这不需要复制/移动。但是这个的返回值将从那个参数复制。所以这是 1 个副本和 2 个移动/副本。

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


您的问题似乎真的是参数 remain 是否从 C++17 中的省略中排除。因为they were already excluded in prior versions。这不会改变;从省略中排除参数的原因尚未失效。

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

【讨论】:

其中一些将是移动 - 你的意思是 1 复制和 3 移动? 把 Nicol 关于参数副本的评论换一种说法:按值取参数仅有助于在输入的过程中进行复制省略,仅在消费时才有意义 i> 参数,即std::move()ing 在别处,例如:X rc(std::move(lhs)); rc += rhs; return rc; @Barry:是的,我忘了返回局部变量的动作是自动的。 我想如果这整件事可以编译成 X sum(a); sum += b; sum += c; sum += d; 的等价物,那就太酷了。猜猜这仍然很难。

以上是关于保证省略和链式函数调用的主要内容,如果未能解决你的问题,请参考以下文章

链式调用就是promise的优点吗?

JavaScript链式调用

如何使用 jest 模拟链式函数调用?

Java链式构造函数调用错误[重复]

Dijit 小部件构造函数抛出“调用链式构造函数”错误

Javscript的函数链式调用基础篇