Tie*_*inë 12 c++ rvalue-reference move-semantics c++11
我正在使用C++编写一个小型数值分析库.我一直在尝试使用最新的C++ 11功能实现,包括移动语义.我理解以下帖子中的讨论和最佳答案:C++ 11 rvalues和移动语义混淆(return语句),但有一种情况我仍然试图包围我的头脑.
我有一个类,称之为T
,它配备了重载运算符.我也有复制和移动构造函数.
T (const T &) { /*initialization via copy*/; }
T (T &&) { /*initialization via move*/; }
Run Code Online (Sandbox Code Playgroud)
我的客户端代码大量使用运算符,所以我试图确保复杂的算术表达式从移动语义中获得最大的好处.考虑以下:
T a, b, c, d, e;
T f = a + b * c - d / e;
Run Code Online (Sandbox Code Playgroud)
没有移动语义,我的操作符每次都使用复制构造函数创建一个新的局部变量,所以总共有4个副本.我希望通过移动语义,我可以将其减少到2个副本加上一些动作.在括号中:
T f = a + (b * c) - (d / e);
Run Code Online (Sandbox Code Playgroud)
每个(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);
}
Run Code Online (Sandbox Code Playgroud)
如果没有调用std::move
,则只const &
调用每个运算符的版本.但是当std::move
如上使用时,使用&&
每个运算符的版本执行后续算法(在最内层表达式之后).
我知道RVO可以被抑制,但在计算成本非常高的现实问题上,似乎收益略微超过了RVO的缺乏.也就是说,超过数百万次计算,当我加入时,我确实得到了非常小的加速std::move
.虽然说实话,但没有足够快.我真的只想完全理解这里的语义.
是否有一位C++ Guru愿意花时间以简单的方式解释我是否以及为何使用std :: move在这里是一件坏事?提前谢谢了.
您应该更喜欢将运算符重载为自由函数以获得完全类型对称(可以在左侧和右侧应用相同的转换).这使得你在问题中遗漏的内容更加明显.将您的操作员重新设置为您提供的免费功能:
T operator+( T const &, T const & );
T operator+( T const &, T&& );
Run Code Online (Sandbox Code Playgroud)
但是你没有提供一个处理左侧是临时的版本:
T operator+( T&&, T const& );
Run Code Online (Sandbox Code Playgroud)
并且当两个参数都是rvalues时,为了避免代码中出现歧义,您需要提供另一个重载:
T operator+( T&&, T&& );
Run Code Online (Sandbox Code Playgroud)
常见的建议是实现+=
作为修改当前对象的成员方法,然后编写operator+
为修改接口中相应对象的转发器.
我没有真正想过这么多,但可能有一个替代方法T
(没有r/lvalue引用),但我担心它不会减少你需要提供的重载次数,以便operator+
在所有情况下都能提高效率.
以其他人所说的为基础:
std::move
in 的调用T::operator+( T const & )
是不必要的,可以防止RVO.operator+
委托的非成员T::operator+=( T const & )
.我还想补充一点,完美的转发可以用来减少所需的非成员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;
}
Run Code Online (Sandbox Code Playgroud)
对于一些运算符来说,这个"通用"版本就足够了,但由于加法通常是可交换的,我们可能想检测右手操作数何时是右值并修改它而不是移动/复制左手操作数.这需要一个版本作为左值的右侧操作数:
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;
}
Run Code Online (Sandbox Code Playgroud)
另一个是右手操作数,它们是右值:
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;
}
Run Code Online (Sandbox Code Playgroud)
最后,您可能也对Boris Kolpackov和Sumant Tambe提出的技术以及Scott Meyers 对这个想法的回应感兴趣.