在超载算术中移动语义和传递引用
Move Semantics and Pass-by-Rvalue-Reference in Overloaded Arithmetic
我正在编码C 中的小数字分析库。我一直在尝试使用最新的C 11功能来实现,包括移动语义。我了解以下文章的讨论和最佳答案:C 11 rvalues并移动语义混乱(返回语句),但是我仍然试图缠绕我的头。
我有一个类,称其为T
,该 CC_1完全配备了过载的操作员。我也有复制和移动构造函数。
T (const T &) { /*initialization via copy*/; }
T (T &&) { /*initialization via move*/; }
我的客户端代码大量使用运算符,因此我正在尝试确保复杂的算术表达式从移动语义中获得最大收益。考虑以下内容:
T a, b, c, d, e;
T f = a + b * c - d / e;
没有移动语义,我的操作员每次都使用复制构造函数制作新的本地变量,因此总共有4个副本。我希望通过移动语义,我可以将其减少到2份以及一些动作。在括号的版本中:
T f = a + (b * c) - (d / e);
(b * c)
和 (d / e)
中的每一个都必须以通常的方式创建临时性,但是如果我可以利用其中一个临时性来积累剩余的结果,那就太好了。
使用G 编译器,我已经可以做到这一点,但是我怀疑我的技术可能不安全,我想完全理解原因。
这是加法操作员的示例实现:
T operator+ (T const& x) const
{
T result(*this);
// logic to perform addition here using result as the target
return std::move(result);
}
T operator+ (T&& x) const
{
// logic to perform addition here using x as the target
return std::move(x);
}
没有拨打std::move
的呼叫,因此仅调用每个操作员的const &
版本。但是,当使用上述std::move
时,使用每个操作员的&&
版本执行后续算术(最终表达式之后)。
我知道RVO可以被抑制,但是在计算廉价的,现实世界中的问题上似乎略大于缺乏RVO。也就是说,当我包括std::move
时,我确实会得到非常小的速度。尽管老实说,没有足够的速度。我真的只想在这里完全理解语义。
是否有一种c 上的大师愿意花时间以一种简单的方式解释我对std ::移动的使用是否以及为什么在这里是一件坏事?非常感谢。边)。这使得您从问题中缺少的东西更为明显。将操作员重申为您提供的免费功能:
T operator+( T const &, T const & );
T operator+( T const &, T&& );
,但是您未能提供一个暂时处理左手侧的版本:
T operator+( T&&, T const& );
,要避免在两个参数为rvalues时,您需要提供另一个过载:
T operator+( T&&, T&& );
常见的建议是将+=
实现为修改当前对象的成员方法,然后将operator+
写为转发器,以修改接口中的适当对象。
我并没有真正考虑过太多,但是使用T
(无R/LVALUE参考)可能会有替代方案,但是我担心它不会减少您需要提供的超载数量,以使operator+
在任何情况下都有效率。
-
T::operator+( T const & )
中对std::move
的呼叫是不必要的,并且可以防止RVO。 - 最好提供委派给
T::operator+=( T const & )
的非会员operator+
。
我还想补充说,可以使用完美的转发来减少所需的非会员operator+
过载的数量:
template< typename L, typename R >
typename std::enable_if<
std::is_convertible< L, T >::value &&
std::is_convertible< R, T >::value,
T >::type operator+( L && l, R && r )
{
T result( std::forward< L >( l ) );
result += r;
return result;
}
对于某些运营商而言,此"通用"版本就足够了,但是由于添加通常是交换性的,因此我们可能想检测右侧操作数何时是rvalue并进行修改,而不是移动/复制左手操作。。这需要一个用于右手操作数的版本:
template< typename L, typename R >
typename std::enable_if<
std::is_convertible< L, T >::value &&
std::is_convertible< R, T >::value &&
std::is_lvalue_reference< R&& >::value,
T >::type operator+( L && l, R && r )
{
T result( std::forward< L >( l ) );
result += r;
return result;
}
,另一个用于右手操作数,这是rvalues:
template< typename L, typename R >
typename std::enable_if<
std::is_convertible< L, T >::value &&
std::is_convertible< R, T >::value &&
std::is_rvalue_reference< R&& >::value,
T >::type operator+( L && l, R && r )
{
T result( std::move( r ) );
result += l;
return result;
}
最后,您也可能对Boris Kolpackov和Sumant Tambe提出的一种技术以及Scott Meyers对这个想法的回应感兴趣。
我同意戴维·罗德里格斯(DavidRodríguez
我很惊讶您在写作时会看到性能退化
T operator+(const T&)
{
T result(*this);
return result;
}
而不是
T operator+(const T&)
{
T result(*this);
return std::move(result);
}
因为在前一种情况下,编译器应该能够使用RVO在内存中构造result
以构建函数的返回值。在后一种情况下,编译器将需要将result
移至函数的返回值中,因此会产生移动的额外费用。
通常,这种事情的规则是,假设您有一个函数返回对象(即,不是参考):
- 如果您要返回本地对象或副价值参数,请不要对其应用
std::move
。这允许编译器执行RVO,该RVO比副本便宜或移动。 - 如果您要返回类型RVALUE参考的参数,请将
std::move
应用于它。这将参数变成了一个rvalue,因此允许编译器从中移动。如果您只是返回参数,则编译器必须执行副本到返回值中。 - 如果您要返回一个通用引用的参数(即推论类型的"
&&
"参数,该参数可能是RVALUE参考或LVALUE参考),请将std::forward
应用于它。没有它,编译器必须在返回值中执行副本。使用它,如果引用绑定到rvalue,则可以执行编译器。
- 何时在引用或唯一指针上使用移动语义
- 在C++17中,引用const字符串的语义应该是什么
- Xcode 语义问题引用或以前定义的代码
- 使用移动语义:右值引用作为方法参数
- <T>通过引用传输向量<shared_ptr>而不绕过共享语义
- C 移动语义:将唯一_ptr引用的矩阵附加到向量
- 我应该如何确保对移动构造函数的调用?(移动语义和右值引用)
- C/C++ 函数中的引用、指针或值语义
- 在超载算术中移动语义和传递引用
- 移动语义和常量引用
- 有人能解释一下这些编程术语吗:引用语义,非线性可变状态
- 如果右值没有绑定到const引用,这将如何影响移动语义和完美转发
- 使用 LValue 引用移动语义
- C++Rvalue引用和移动语义
- 通过移动语义和右值引用改进了性能
- 如何使用boost::expression-static在语义操作中引用可选子匹配
- 移动语义和引用语义
- range-v3 的"partial_sum"如何不与非拥有引用语义相矛盾?
- C 引用语义在调用方的代码中不显式
- 持有大共享状态的访问者类:实现引用语义的最佳方式