保证省略和链式函数调用
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;
的等价物,那就太酷了。猜猜这仍然很难。以上是关于保证省略和链式函数调用的主要内容,如果未能解决你的问题,请参考以下文章