2012-08-05 63 views
38

考慮支持默認移動語義的類型T.還要考慮以下功能:從函數返回值時使用std :: move()以避免複製

T f() { 
    T t; 
    return t; 
} 

T o = f(); 

在舊的C++ 03,一些非最優的編譯器可能會調用拷貝構造函數兩次,一爲「返回對象」,一個針對o

在C++ 11中,因爲t裏面的f()是一個左值,所以這些編譯器可能會像以前一樣調用複製構造函數,然後調用o的移動構造函數。

是否正確指出避免第一個「額外複製」的唯一方法是在返回時移動t

T f() { 
    T t; 
    return std::move(t); 
} 
+0

[here](http://stackoverflow.com/questions/9827183/why-am-i-allowed-to-copy-unique-ptr)是一個類似的問題 – 2012-08-05 18:30:23

+0

[相關FAQ] (http://stackoverflow.com/a/11540204/252000) – fredoverflow 2012-08-06 08:17:46

回答

42

號每當在return聲明一個局部變量可享有複製省略,它結合一個rvalue重新­ FE ­倫斯,因此return t;相同return std::move(t);在例如相對於該構造是合格。

但是請注意,return std::move(t);可以防止從編譯器執行copy elision,而return t;沒有,因此後者是首選的風格。 [感謝@Johannes的回覆。]如果發生複製刪除,使用移動構造的問題就成了一個爭議點。

請參閱標準中的12.8(31,32)。

還要注意的是,如果T具有可訪問的禁止複製,但已刪除的舉動構造函數,然後return t;不會COM ­樁,因爲移動構造函數必須首先考慮;你不得不說點什麼到EF ­ FECT的return static_cast<T&>(t);,使其工作:

T f() 
{ 
    T t; 
    return t;     // most likely elided entirely 
    return std::move(t);  // uses T::T(T &&) if defined; error if deleted or inaccessible 
    return static_cast<T&>(t) // uses T::T(T const &) 
} 
+0

謝謝。移動構造函數的調用次數如何?它可以是兩個作爲複製構造函數與一些C++ 03-抱怨編譯器的調用次數嗎? – Martin 2012-08-05 16:07:35

+12

它*不*相同。關於是否可以調用移動構造函數只是相同的。如果你寫'return std :: move(t);'如果編譯器不知道它的作用,就必須調用移動構造函數*。如果你寫'return t;'移動構造函數調用可以被忽略,即使它可能有副作用。 – 2012-08-05 16:08:26

+0

@ JohannesSchaub-litb:好的,讓我編輯一下。 – 2012-08-05 16:10:18

3

好吧,我想在這個下降的註釋。這個問題(和答案)讓我相信,沒有必要在return語句中指定std::move。然而,在處理我的代碼時,我只是被認爲是一個不同的課程。

所以,我有一個函數(它實際上是一個專業化),需要一個臨時的,並只是返回它。 (通用函數模板執行其他操作,但專業化操作執行身份操作)。

template<> 
struct CreateLeaf<A> 
{ 
    typedef A Leaf_t; 
    inline static 
    Leaf_t make(A &&a) { 
    return a; 
    } 
}; 

現在,這個版本在返回時調用A的拷貝構造函數。如果我改變了return語句

Leaf_t make(A &&a) { 
    return std::move(a); 
} 

隨後的A移動構造函數被調用,我可以做一些優化那裏。

它可能不是100%符合你的問題。但認爲return std::move(..)從不需要是錯誤的。我曾經這麼認爲。沒有更多;-)

+0

這與原始問題不同。原來的問題是關於return x,其中x是局部變量。當x是局部變量時,返回x更好,因爲編譯器會將x視爲返回值中的右值,因爲它知道x是局部變量。當x是一個引用時,編譯器不會給它特殊的處理。由於您的示例中「a」變量的類型是「A&」,因此您需要使用move將其更改爲「A &&」。 – 2017-09-21 00:25:48

8

不。最佳做法是直接return t;

如果類T具有移動構造不會被刪除,並通知t是一個局部變量return t可享有複製省略,其移動結構返回的對象,就像return std::move(t);一樣。但是return t;仍然有資格複製/移動elision,因此可以省略構造,而return std::move(t)總是使用移動構造函數構造返回值。

如果類T中的移動構造函數被刪除但複製構造函數可用,則return std::move(t);將不會編譯,而return t;仍使用複製構造函數編譯。與提到的@Kerrek不同,t未綁定到右值引用。對於符合複製限制條件的返回值,有兩階段重載解決方案 - 嘗試先移動然後複製,並且移動和複製都可能被取消。

class T 
{ 
public: 
    T() = default; 
    T (T&& t) = delete; 
    T (const T& t) = default; 
}; 

T foo() 
{ 
    T t; 
    return t;     // OK: copied, possibly elided 
    return std::move(t);  // error: move constructor deleted 
    return static_cast<T&>(t); // OK: copied, never elided 
} 

如果return表達式是左值,並沒有資格複製省略(最可能是你正在返回一個非本地變量或左值表達式),你還是想避免複製,std::move將是有益的。但請記住,最好的做法是使複製elision可能發生。

class T 
{ 
public: 
    T() = default; 
    T (T&& t) = default; 
    T (const T& t) = default; 
}; 

T bar(bool k) 
{ 
    T a, b; 
    return k ? a : b;   // lvalue expression, copied 
    return std::move(k ? a : b); // moved 
    if (k) 
     return a;    // moved, and possibly elided 
    else 
     return b;    // moved, and possibly elided 
} 

12.8(32)中的標準描述了該過程。

12.8 [class.copy]

32當的複製操作的省音的標準被滿足或一個事實,即源對象是一個函數參數,並且是對象將被滿足節省複製是由一個左值指定的,重載解析選擇複製的構造函數首先執行,就好像該對象是由右值指定的一樣。如果重載解析失敗,或者如果所選構造函數的第一個參數的類型不是對對象類型的右值引用(可能是cv-qualified),則將該對象視爲左值,重新執行重載解析。 [注意:無論是否發生複製刪除,都必須執行這種兩階段重載解析。如果不執行elision,它將確定要調用的構造函數,即使該函數沒有被使用,所選的構造函數也必須是可訪問的。 - 注意]

相關問題