2011-05-26 23 views
14

我仍然是一名菜鳥程序員,我知道過早的優化是不好的,但我也知道,複製巨大的東西也是不好的。編譯器能否刪除以下副本?

我讀過複製elision和它的同義詞,但維基百科上的例子,例如,使我覺得複製elision只能發生,如果要返回的對象得到返回,同時它被完全構造。

對於像矢量這樣的對象,當用作返回值時,通常只有在填充某些東西時纔有意義。畢竟,一個空向量只能手動實例化。

那麼,它是否也適用於這種情況?

不好的風格爲簡潔:

vector<foo> bar(string baz) 
{ 
    vector<foo> out; 
    for (each letter in baz) 
     out.push_back(someTable[letter]); 

    return out; 
} 

int main() 
{ 
    vector<foo> oof = bar("Hello World"); 
} 

我使用欄中沒有真正的麻煩(矢量&出來,字符串文本),但上述方式看起來好多了,美觀,並意圖。

+3

它可以被忽略。但是,請注意,標準_still_要求複製構造函數可訪問(例如非私有的) – sehe 2011-05-26 13:28:17

回答

10

維基百科例如例子使它看起來對我來說,複製省略,如果要返回的對象在它被完全構造的同時獲取返回纔會發生。

這是誤導(閱讀:錯誤)。問題是,只有一個對象返回所有代碼路徑,即只有一個構造潛在的返回對象正在發生。

你的代碼很好,任何現代編譯器都可以刪除副本。

在另一方面,下面的代碼可能會產生問題:

vector<int> foo() { 
    vector<int> a; 
    vector<int> b; 
    // … fill both. 
    bool c; 
    std::cin >> c; 
    if (c) return a; else return b; 
} 

這裏,編譯器需要全面構建兩個不同的對象,只有決定哪些人返回,因此它必須複製一次,因爲它不能直接在目標內存位置構造返回的對象。

+0

您的反例實際上是在我的回答中引用的同一段落(但是其中的另一部分)明確免除了copy-elision。只有在return-statement中的表達式是類對象的名稱時,才允許使用elision。 – 2011-05-26 13:33:59

+0

可能編譯器決定_move_返回的向量? – user396672 2011-05-26 13:39:42

+0

@ user396672我在C++ 0x中並不堅定,但是邏輯規定這應該是可能的(因爲在return之後原來不再需要)。 – 2011-05-26 13:50:02

5

沒有什麼能夠阻止編譯器刪除副本。這在15年8月12日所定義:

[...]的複製操作的這個省音是 允許在下列情況 (其可以被組合 消除多個副本):

[。 ..

  • 當已經 尚未綁定到一個參考臨時類對象(12.2) 將被複制到一個類對象與 相同CV-限定類型,與c OPY 操作可以通過 直接構建臨時對象 到 省略副本

如果它實際上取決於編譯器和使用設置的目標被省略。

+0

,它在我看來就像是關於該主題的一般答案,如下所示:「如果編譯器足夠聰明以檢測機會複製,即使要返回的對象要在整個返回函數中編輯,也會這樣做。「,是嗎?編輯:啊,那個片段是內容豐富的,如果它在我閱讀的文章中對我感到羞恥......謝謝! – Erius 2011-05-26 13:30:05

+0

@Erius:它只取決於兩件事情:如果編譯器足夠聰明,並且允許這樣做。我只能回答第一個,因爲我不知道你的編譯器和你的設置。 – 2011-05-26 13:31:44

+0

嗯,這是(希望最新的)MSVC之一,完全優化,但我現在完全理解編譯器智能部分,所以再次感謝。 – Erius 2011-05-26 13:41:55

5

這兩個vector的暗示副本都可以 - 而且經常被 - 刪除。指定的返回值優化可以消除返回語句return out;中隱含的副本,並且可以消除oof副本初始化中隱含的臨時副本。

在這兩種優化中,在vector<foo> out;中構造的對象與oof的對象相同。

用這樣的仿真測試用例來測試這些優化中的哪一個正在執行更容易。

struct CopyMe 
{ 
    CopyMe(); 
    CopyMe(const CopyMe& x); 
    CopyMe& operator=(const CopyMe& x); 

    char data[1024]; // give it some bulk 
}; 

void Mutate(CopyMe&); 

CopyMe fn() 
{ 
    CopyMe x; 
    Mutate(x); 
    return x; 
} 

int main() 
{ 
    CopyMe y = fn(); 
    return 0; 
} 

複製構造函數是聲明的但未定義的,以便對它的調用不能被內聯和消除。用現在比較老的gcc 4.4編譯,在處給出以下程序集(經過濾除C++名稱並編輯刪除非代碼)。

fn(): 
     pushq %rbx 
     movq %rdi, %rbx 
     call CopyMe::CopyMe() 
     movq %rbx, %rdi 
     call Mutate(CopyMe&) 
     movq %rbx, %rax 
     popq %rbx 
     ret 

main: 
     subq $1032, %rsp 
     movq %rsp, %rdi 
     call fn() 
     xorl %eax, %eax 
     addq $1032, %rsp 
     ret 

可以看出,沒有對拷貝構造函數的調用。事實上,即使在-O0,gcc也會執行這些優化。您必須提供-fno-elide-constructors以關閉此行爲;如果你這樣做,那麼gcc會生成兩個對CopyMe的拷貝構造函數的調用 - 一個在fn()的調用內部和一個外部。

fn(): 
     movq %rbx, -16(%rsp) 
     movq %rbp, -8(%rsp) 
     subq $1048, %rsp 
     movq %rdi, %rbx 
     movq %rsp, %rdi 
     call CopyMe::CopyMe() 
     movq %rsp, %rdi 
     call Mutate(CopyMe&) 
     movq %rsp, %rsi 
     movq %rbx, %rdi 
     call CopyMe::CopyMe(CopyMe const&) 
     movq %rbx, %rax 
     movq 1040(%rsp), %rbp 
     movq 1032(%rsp), %rbx 
     addq $1048, %rsp 
     ret 

main: 
     pushq %rbx 
     subq $2048, %rsp 
     movq %rsp, %rdi 
     call fn() 
     leaq 1024(%rsp), %rdi 
     movq %rsp, %rsi 
     call CopyMe::CopyMe(CopyMe const&) 
     xorl %eax, %eax 
     addq $2048, %rsp 
     popq %rbx 
     ret 
相關問題