2010-01-09 37 views
6

隱含參數最近,我讀(不幸的是忘了在哪裏),最好的方式寫操作=是這樣的:明確的拷貝構造函數或價值

foo &operator=(foo other) 
{ 
    swap(*this, other); 
    return *this; 
} 

的這個代替:

foo &operator=(const foo &other) 
{ 
    foo copy(other); 
    swap(*this, copy); 
    return *this; 
} 

這個想法是,如果operator =用右值調用,那麼第一個版本可以優化構建副本。所以當用右值調用時,第一個版本更快,當用左值調用時,兩者是等價的。

我很好奇其他人對此有何看法?由於缺乏明確性,人們會避免第一個版本嗎?我認爲第一個版本可以更好,永遠不會更糟?

+0

什麼是'swap'?如果它是'foo temp = x; X = Y; y = temp;''你有'operator ='和'swap'函數的無限遞歸。 – 2010-01-09 21:55:42

+1

我寫了一個程序來測試@Alexey Malistov的理論,他是正確的 - 我得到了無限遞歸。 – nobar 2010-01-09 23:26:22

+0

任何實現複製和交換習語的類都不能依賴其複製賦值運算符中的默認'std :: swap'實現。這很不用說。 – 2010-01-09 23:37:48

回答

4

你可能從閱讀:http://cpp-next.com/archive/2009/08/want-speed-pass-by-value/

我沒有什麼好說的,因爲我想的鏈接解釋的理由相當不錯。有趣的是,我可以確認第一種形式使用MSVC生成更少的副本,這很有意義,因爲編譯器可能無法在第二種形式上進行復制刪除。我同意第一種形式是嚴格的改進,永遠不會比第二種形式差。

編輯: 第一種形式可能會少一點慣用,但我認爲它不那麼清楚。 (國際海事組織,它沒有比第一次看到賦值運算符的複製和交換實現更令人驚訝。)

編輯#2:哎呀,我的意思是編寫copy-elision,而不是RVO。

+1

我不同意第一個習慣用法。這是不常見的版本。我認爲,如果你爲一家做大量代碼審查的大公司工作,你會發現自己被以正常方式發回。正常情況下維護更好,因爲人們不需要花費雙倍的工作量就可以瞭解正在發生的事情。我認爲第一個版本只是一個很好的派對伎倆,展示你是多麼的酷。 – 2010-01-09 20:26:30

+1

我並不是說第一種形式是慣用的(我確實說第二種形式更是如此)。但是成語必須從某處開始,而且我知道第二種形式讓我在第一次看到它時做了兩次嘗試。 – jamesdlin 2010-01-09 20:34:22

+1

@Martin:正如Herb Sutter所說,當習俗與良好習慣相沖突時,我們不得不問自己是否應該拋棄慣例而贊成良好的習慣,或放棄有利於慣例的良好習慣。 – 2010-01-09 22:21:17

-3

我認爲你可能會混淆的區別:

foo &operator=(const foo &other);
const foo &operator=(const foo &other);

的第一種形式應該被用於允許:(a = b) = c;

+4

我不認爲他在混淆這些 - 他的問題很有道理。 – 2010-01-09 19:48:15

-2

這兩個其實都是一樣的。唯一的區別是你在調試器中按下「Step In」。而且你應該事先知道在哪裏做。

2

我通常更喜歡第二個來自可讀性和「最少驚喜」的觀點,但是我確實承認,當參數是臨時參數時,第一個效率更高。

第一個真的可以導致沒有副本,而不僅僅是單個副本,可以想象,這可能是在極端情況下的真正關注。

E.g.參加這個測試程序。 gcc -O3 -S(gcc版本4.4.2 20091222(Red Hat 4.4.2-20)(GCC))會爲B的拷貝構造函數生成一個調用,但不會調用函數f的A的拷貝構造函數(賦值操作符爲AB )。 AB都可以被視爲非常基本的字符串類。 data的分配和複製將發生在構造函數中,並在析構函數中解除分配。

#include <algorithm> 

class A 
{ 
public: 
    explicit A(const char*); 
    A& operator=(A val)  { swap(val); return *this; } 
    void swap(A& other)  { std::swap(data, other.data); } 
    A(const A&); 
    ~A(); 

private: 
    const char* data; 
}; 

class B 
{ 
public: 
    explicit B(const char*); 
    B& operator=(const B& val) { B tmp(val); swap(tmp); return *this; } 
    void swap(B& other)   { std::swap(data, other.data); } 
    B(const B&); 
    ~B(); 

private: 
    const char* data; 
}; 

void f(A& a, B& b) 
{ 
    a = A("Hello"); 
    b = B("World"); 
} 
+0

值得注意的是,這兩個類都沒有任何數據成員。另外,你是否可以編輯你的例子,以儘量減少垂直空白空間? – 2010-01-09 20:08:29

+0

@Neil:數據成員沒有任何區別,我只是在測試工具中爲這兩個結構添加了一個'long data [32];'。從理論上講,數據成員可能會被我未定義的構造函數觸動。生成給構造函數的調用保持不變,一次調用'B :: B(const B&)',零次調用'A :: A(const A&)'。 – 2010-01-09 20:14:49

+0

啊,我想我明白你的意思了。我添加了聲明但未定義的構造函數,複製構造函數,析構函數和void *數據成員的類類型(類C)的另一個數據成員。儘管生成了一些額外的異常處理代碼,並且在預期的地方調用了'C ::〜C()',調用'B :: B(const B&)'和'A :: A(const A&)'。這是否會造成任何真實世界的差異是有爭議的,但這種可能性當然存在。 – 2010-01-09 20:46:17

0

鑑於這種

foo &foo::operator=(foo other) {/*...*/ return *this;} 
foo f(); 
代碼

這樣

foo bar; 
bar = f(); 

它可能是一個編譯器,以消除調用拷貝構造更簡單。使用RVO,它可以使用運營商的other參數的地址作爲f()的位置來構建其返回值。

看起來這種優化對於第二種情況也是可能的,儘管我相信這可能會更困難。 (特別是當操作員不內聯時。)