假设我有以下类型:
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 中,我对这个表达式将执行多少个副本和移动有什么保证?非保证省略怎么办?
假设我有以下类型:
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 个复制构造和 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 次移动(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
不这样做)。
好的,让我们从这个开始:
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 中的省略之外。因为它们已经在以前的版本中被排除了。这不会改变;从省略中排除参数的原因尚未失效。
“保证省略”仅适用于纯右值。如果它有名称,则它不能是纯右值。