2013-03-25 62 views
4

例如:的std ::移動與編譯器優化

void f(T&& t); // probably making a copy of t 

void g() 
{ 
    T t; 
    // do something with t 
    f(std::move(t)); 
    // probably something else not using "t" 
} 

void f(T const& t)相當於在這種情況下,因爲任何好的編譯器會產生相同的代碼?如果這很重要,我對> = VC10和> = GCC 4.6感興趣。

編輯:

基礎上的答案,我想闡述的問題有點:

比較rvalue-referencepass-by-value方法,它是如此容易忘記在pass-by-value使用std::move。編譯器是否仍然可以檢查是否沒有對變量進行更改並消除不必要的副本?

rvalue-reference方法僅使優化版本「隱含」,例如,並且要求用戶明確指定其他情況,例如f(std::move(t)),或者如果用戶沒有完成t實例,則明確地製作副本f(T(t));。那麼,在這個優化相關的燈光中,rvalue-reference的方法算不錯了?

+1

不會有很多次你應該考慮通過實現移動構造函數時比其他右值引用的論據。而且幾乎總是應該重載一個函數,該函數通過'const'左值引用。 – 2013-03-25 11:59:24

+0

@sftrabbit:請編輯的問題 – 2013-03-25 12:30:46

+0

你不需要的std ::移動(T(T),只是T(T)就足夠了,因爲它是明確的牛逼此功能 – Alon 2013-03-25 12:34:22

回答

5

這絕對是不一樣的。一次T &&只能只有綁定到rvalues,而T const &可以綁定到rvalues和左值。其次,T const &不允許任何移動優化。如果您「可能想複製t」,那麼T &&允許您實際製作t的移動副本,這可能更有效。

實施例:

void foo(std::string const & s) { std::string local(s); /* ... */ } 

int main() 
{ 
    std::string a("hello"); 
    foo(a); 
} 

在該代碼中,含有"hello"字符串緩衝區必須存在兩次,一次在的main體內,並在foo主體的另一時間。相比之下,如果使用右值引用和std::move(a),那麼可以「移動」相同的字符串緩衝區,只需要一次性分配和填充字符串緩衝區。

由於@Alon指出,正確的成語,其實是passing-by-value

void foo(std::string local) { /* same as above */ } 

int main() 
{ 
    std::string a("hello"); 
    foo(std::move(a)); 
} 
+0

請編輯的問題 – 2013-03-25 12:31:40

+0

@KerrekSB,是啊,請檢查編輯過的問題......另外,你的「for one」這個句子聽起來像是「lawyery」和迴避;其餘的聽起來像是真正的答案。 – einpoklum 2017-08-07 19:00:43

2

嗯,這取決於什麼呢˚F與T,如果它創建一個副本,然後我會去,即使在這樣的長度:

void f(T t) // probably making a copy of t 
{ 
    m_newT = std::move(t); // save it to a member or take the resources if it is a c'tor.. 
} 

void g() 
{ 
    T t; 
    // do something with t 
    f(std::move(t)); 
    // probably something else not using "t" 
} 

然後你讓移動c'tors優化在任何情況下,你都需要't'資源,如果它被「移動」到你的函數中,那麼你甚至可以獲得將它移動到函數的非副本,如果它沒有移動,那麼你可能必須有一個複製

現在,如果在以後的代碼你必須:

f(T()); 

然後ただ,自由移動優化甚至不知道F用戶..

需要注意的是一句話:「是無效的F(T const的& T),相當於在這種情況下,因爲任何好的編譯器會產生相同的代碼? 「

它不是equivelent,這是較少的工作,因爲只有「指針」被轉移並沒有c'tors被稱爲可言,既不移動也不是別的

+0

你爲什麼要製作一個本地副本,然後*然後*將其移入另一個本地副本? – juanchopanza 2013-03-25 11:48:16

+0

@juanchopanza:其實你不復制一個本地副本,因爲此舉c'tor被調用,當你必須做出一個本地副本(然後利用其資源,所以它是一個副本,無論哪種方式),你不能使用移動 – Alon 2013-03-25 11:49:18

+0

@juanchopanza:如果'f'是一個構造函數呢? – 2013-03-25 11:50:43

1

以一個const左值參考和服用右值引用是兩回事。

相似之處:

  • 亦不會導致複製或移動到發生,因爲他們都是引用。引用只是引用一個對象,它不會以任何方式複製/移動它。

差異:

  • const左值參考將結合到任何東西(左值或右值)。右值引用只會綁定到非const右值 - 更有限。

  • 函數內部的參數是const左值引用時無法修改。當它是一個右值引用時它可以被修改(因爲它是非const)。

讓我們來看一些例子:

  1. const左值參考:void f(const T& t);

    1. 傳遞一個左:

      T t; f(t); 
      

      這裏,t是一個左值表達式,因爲它是對象的名稱。 A const左值引用可以綁定任何內容,因此t將很樂意通過引用傳遞。沒有任何東西被複制,什麼都不移動

    2. 傳遞右值:

      f(T()); 
      

      這裏,T()是一個右值表達,因爲它產生一個臨時的物體。再次,一個const左值引用可以綁定到任何東西,所以這沒關係。沒有任何東西被複制,什麼都不移動

    在這兩種情況下,該函數內的t是傳入的對象的引用,它不能由參考被修改是const

  2. 取右值參考:`void f(T & & t);

    1. 傳遞一個左:

      T t; 
      f(t); 
      

      這會給你一個編譯器錯誤。右值引用不會綁定到左值。

    2. 傳遞右值:

      f(T()); 
      

      這將是很好的,因爲一個rvalue參考可結合一個rvalue。函數內部的參考t將引用由T()創建的臨時對象。

現在讓我們考慮std::move。首先要做的事情是:std::move實際上並沒有移動任何東西。這個想法是,你給它一個左值,並把它變成一個右值。就是這樣。所以,現在,如果你的f需要一個右值引用,你可以這樣做:

T t; 
f(std::move(t)); 

這樣做是因爲,雖然t是一個左值,std::move(t)是一個右值。現在右值引用可以綁定到它。

那麼,爲什麼你會採取一個右值引用參數?事實上,除了定義移動構造函數和賦值運算符外,您不需要經常這樣做。無論何時定義一個帶右值引用的函數,幾乎肯定會給出一個const左值引用超載。他們應該幾乎總是成對出現:

void f(const T&); 
void f(T&&); 

爲什麼這對功能有用?那麼,只要你給它一個左值(或一個const右值),第一個就會被調用,而第二個會在你給它一個可修改的右值時被調用。接收一個右值通常意味着你已經得到一個臨時對象,這是一個好消息,因爲這意味着你可以摧毀它的內部並根據事實知道它不會存在很久。

因此擁有這對功能可以讓您在知道自己獲得臨時對象時進行優化。

這對函數有一個非常常見的例子:複製和移動構造函數。他們通常被定義就像這樣:

T::T(const T&); // Copy constructor 
T::T(T&&); // Move constructor 

所以此舉構造是真的只是正在接收一個臨時對象時優化的拷貝構造函數。

當然,被傳遞的對象不是總是是一個臨時對象。如上所示,您可以使用std::move將左值變爲右值。然後它出現是該函數的臨時對象。使用std::move基本上說「我允許你把這個對象當作臨時對象。」它是否實際上被移動或不移動是無關緊要的。

但是,除了編寫複製構造函數和移動構造函數之外,最好有一個很好的理由來使用這對函數。如果你正在編寫一個接受對象的函數,並且它的行爲與它是完全一樣的,不管它是否是一個臨時對象,只需通過值來獲取該對象!考慮:

void f(T t); 

T t; 
f(t); 
f(T()); 

在第一次調用f時,我們傳遞了一個左值。那將複製到進入函數。在第二次調用f時,我們傳遞了一個右值。該對象將轉移到的功能中。請參閱 - 我們甚至不需要使用右值引用來使對象有效地移動。我們只是看重它!爲什麼?因爲用於進行復制/移動的構造函數是根據表達式是左值還是右值來選擇的。讓複製/移動構造函數完成他們的工作。

對於不同的參數類型是否產生了相同的代碼 - 嗯,這是一個完全不同的問題。編譯器在下運行,如果規則爲。這只是意味着只要程序按照標準規定行事,編譯器就可以發出任何它喜歡的代碼。因此,如果這些函數碰巧做了完全相同的事情,它們可能會發出相同的代碼。或者他們可能不會。然而,如果你的函數使用一個常量左值引用和右值引用正在做同樣的事情,那麼這是一個不好的跡象。