簡體   English   中英

移動語義和運算符重載

[英]Move semantics and operator overloading

這與Matthieu M.提供的關於如何利用+運算符重載(通常是不直接重新分配回左側參數的運算符)一起使用移動語義的答案有關。

他建議實施三種不同的重載:

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會被使用。 有什么我想念的嗎?

我不認為你錯過任何東西 - 你問題中的代碼確實很麻煩。 他的回答的早期部分是有道理的,但在“四個理想案例”和實際例子之間丟失了一些東西。

這可能會更好:

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&& ,因為這是一個懸掛的參考。

有關此答案的重要更新/警告!

實際上一個令人信服的例子 ,它在下面的合理的現實世界代碼中默默地創建了一個懸空引用。 請使用其他答案的技巧來避免此問題,即使以創建一些額外的臨時值為代價。 我將保留此答案的其余部分,以備將來參考。


可交換案例的正確重載是:

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 );
}

為什么這樣,它是如何工作的? 首先,請注意,如果將rvalue引用作為參數,則可以修改並返回它。 它來自的表達式需要保證在完整表達式結束之前不會破壞rvalue,包括operator+ 這也意味着operator+可以簡單地返回rvalue引用,因為調用者需要使用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 );

這意味着你仍然有三個臨時工,雖然他們被移動而不是復制,但上面的方法在完全避免臨時工作方面更有效率。

有關完整的庫,包括對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;
}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM