移动语义和运算符重载(Move semantics and operator overloading

2019-08-31 17:07发布

这与此答案由马修M.如何利用与+运算符重载移动语义提供(一般来说,运营商不重新分配直接回左PARAM)。

他建议实行三种不同的重载:

inline T operator+(T left, T const& right) { left += right; return left; }
inline T operator+(T const& left, T right) { right += left; return right; } // commutative
inline T operator+(T left, T&& right) { left += right; return left; } // disambiguation

号码1和3是有意义的,但我不明白什么目的呢2。 该意见提出交换处理,但似乎1和2是相互排斥的(即实施歧义两个结果)

例如,在所有3个实现:

T a, b, c;
c = a + b;

编译器输出:

1>          error C2593: 'operator +' is ambiguous
1>          could be 'T operator +(const T &,T)'
1>          or       'T operator +(T,const T &)'
1>          while trying to match the argument list '(T, T)'

去除1或2个和节目按预期工作。 由于1是一般的情况和2只用可交换操作正常工作,我不明白为什么2都不会被使用。 是否有什么我失踪?

Answer 1:

我不认为你错过任何东西 - 你的问题的代码确实是麻烦。 他的回答的前面部分是有道理的,但有什么东西在“四个所需的情况下”和实际的例子之间丢失。

这可能是更好的:

inline T operator+(T left, T const& right) { left += right; return left; }
inline T operator+(const T& left, T&& right) { right += left; return right; }

这实现该规则:使LHS的副本(优选通过移动结构)中,除RHS被到期无论如何,在这种情况下,修改它在适当位置。

对于非交换运营商,省略第二过载,否则提供不委托给复合赋值的实现。

如果你的类有嵌入到重量级的资源(以便它不能有效地移动),你要避免通过噪声值。 丹尼尔使得一些很好的意见在他的回答。 但不返回T&&他建议,因为这是一个悬空的参考。



Answer 2:

重要更新/警告这个答案!

这里实际上一个有说服力的例子 ,其安静无声地创造合理的现实世界中的代码与下面悬空参考。 请使用其他答案的技术来避免,即使在被创造了一些额外的临时成本这个问题。 我会离开这个答案不变以供将来参考的其余部分。


对于可交换情况下,正确的重载方法:

T   operator+( const T& lhs, const T& rhs )
{
  T nrv( lhs );
  nrv += rhs;
  return nrv;
}

T&& operator+( T&& lhs, const T& rhs )
{
  lhs += rhs;
  return std::move( lhs );
}

T&& operator+( const T& lhs, T&& rhs )
{
  rhs += lhs;
  return std::move( rhs );
}

T&& operator+( T&& lhs, T&& rhs )
{
  lhs += std::move( rhs );
  return std::move( lhs );
}

这是为什么,它是如何工作的? 首先,请注意,如果你把右值引用作为参数,你可以修改和返回。 它从何而来需求的表达,以保证右值将不完整表达式的末尾,包括之前被销毁operator+ 。 这也意味着, operator+可以简单地返回右值引用作为呼叫者需要使用的结果operator+ (其是相同表达式的一部分)之前的表达被完全评估和临时对象(ravlues)是自毁。

第二个重要的观察是,如何节省了更多的临时和移动操作。 请看下面的表达式:

T a, b, c, d; // initialized somehow...

T r = a + b + c + d;

与上述,它是等效于:

T t( a );    // T operator+( const T& lhs, const T& rhs );
t += b;      // ...part of the above...
t += c;      // T&& operator+( T&& lhs, const T& rhs );
t += d;      // T&& operator+( T&& lhs, const T& rhs );
T r( std::move( t ) ); // T&& was returned from the last operator+

比较这什么与其他方法发生了:

T t1( a );   // T operator+( T lhs, const T& rhs );
t1 += b;     // ...part of the above...
T t2( std::move( t1 ) ); // t1 is an rvalue, so it is moved
t2 += c;
T t3( std::move( t2 ) );
t3 += d;
T r( std::move( t3 );

这意味着你还有3个临时工,尽管它们被移动而不是复制,上面的方法是完全避免的临时有效得多。

对于一个完整的图书馆,包括支持noexcept ,看到df.operators 。 在那里,你还可以找到混合类型的非交换情况下的版本和操作。


下面是一个完整的测试程序进行测试:

#include <iostream>
#include <utility>

struct A
{
  A() { std::cout << "A::A()" << std::endl; }
  A( const A& ) { std::cout << "A::A(const A&)" << std::endl; }
  A( A&& ) { std::cout << "A::A(A&&)" << std::endl; }
  ~A() { std::cout << "A::~A()" << std::endl; }

  A& operator+=( const A& ) { std::cout << "+=" << std::endl; return *this; }
};

// #define BY_VALUE
#ifdef BY_VALUE
A operator+( A lhs, const A& rhs )
{
  lhs += rhs;
  return lhs;
}
#else
A operator+( const A& lhs, const A& rhs )
{
  A nrv( lhs );
  nrv += rhs;
  return nrv;
}

A&& operator+( A&& lhs, const A& rhs )
{
  lhs += rhs;
  return std::move( lhs );
}
#endif

int main()
{
  A a, b, c, d;
  A r = a + b + c + d;
}


文章来源: Move semantics and operator overloading