2009-10-07 54 views
12

考慮一個需要製作副本的類。拷貝中的絕大多數數據元素必須嚴格反映原始數據,但是有少數元素的狀態不會被保留,需要重新初始化從複製構造函數中調用默認賦值運算符是否是錯誤的形式?

從複製構造函數調用默認賦值運算符是否是錯誤的形式?

對於每個賦值運算符,默認賦值運算符對於普通舊數據(int,double,char,short)以及用戶定義的類都會表現良好。指針需要分開處理。

其中一個缺點是,由於不執行額外的重新初始化,因此此方法會使賦值運算符癱瘓。禁止使用賦值運算符也是不可能的,因此打開用戶的選項來使用不完整的默認賦值運算符A obj1,obj2; obj2=obj1; /* Could result is an incorrectly initialized obj2 */創建一個破碎的類。

這將是很好的放鬆要求,a(orig.a),b(orig.b)...除了a(0),b(0) ...必須寫。需要編寫所有的初始化兩次創建兩個地方的錯誤,如果新的變量(比如double x,y,z)將被添加到類,初始化代碼將需要至少2個地方,而不是1

正確添加有沒有更好的辦法?

在C++ 0x中有更好的方法嗎?

class A { 
    public: 
    A(): a(0),b(0),c(0),d(0) 
    A(const A & orig){ 
     *this = orig;  /* <----- is this "bad"? */ 
     c = int(); 
    } 
    public: 
    int a,b,c,d; 
}; 

A X; 
X.a = 123; 
X.b = 456; 
X.c = 789; 
X.d = 987; 

A Y(X); 

printf("X: %d %d %d %d\n",X.a,X.b,X.c,X.d); 
printf("Y: %d %d %d %d\n",Y.a,Y.b,Y.c,Y.d); 

輸出:

X: 123 456 789 987 
Y: 123 456 0 987 

替代拷貝構造函數:

A(const A & orig):a(orig.a),b(orig.b),c(0),d(orig.d){} /* <-- is this "better"? */ 
+2

請參閱:http://stackoverflow.com/questions/1457842/is-this-good-code-copyctor-operator – sth 2009-10-07 19:57:40

回答

4

隨着你的拷貝構造函數的版本的成員是第一個缺省構造的,然後分配。
使用整數類型這並不重要,但是如果您有像std::string這樣的非平凡成員,這是不必要的開銷。

因此,是的,一般來說,你的替代拷貝構造函數更好,但如果你只有整數類型作爲成員,它並不重要。

13

正如brone指出的那樣,您最好在複製構造方面實施作業。我更喜歡另一種成語他:

T& T::operator=(T t) { 
    swap(*this, t); 
    return *this; 
} 

這是一個有點短,can take advantage of some esoteric language features以提高性能。就像任何優秀的C++代碼一樣,它也有一些細節需要注意。

首先,t參數是有意傳遞的值,這樣拷貝構造函數會被調用(most of the time),我們可以修改是爲了我們的心的內容而不影響原來的值。使用const T&將無法​​編譯,並且T&會通過修改指定的值來引發一些令人驚訝的行爲。

該技術還要求swap專門針對該類型,而不使用該類型的賦值運算符(如std::swap那樣),否則會導致無限遞歸。請小心任何零散的using std::swapusing namespace std,因爲它們會將std::swap拉到範圍內,並且如果您沒有專門針對T專門設計swap,會導致問題。過載分辨率和ADL將確保使用正確版本的交換,如果您已經定義它。

有幾種方法可以爲類型定義swap。第一種方法使用一個swap成員函數來完成實際工作,具有swap專門委託給它,像這樣:

class T { 
public: 
    // .... 
    void swap(T&) { ... } 
}; 

void swap(T& a, T& b) { a.swap(b); } 

這是在標準庫中很常見的;例如,std::vector以這種方式實現了交換。如果您有一個swap成員函數,您可以直接從賦值運算符調用它,並避免函數查找出現任何問題。

另一種方式是聲明swap爲友元函數,並讓它做所有的工作:

class T { 
    // .... 
    friend void swap(T& a, T& b); 
}; 

void swap(T& a, T& b) { ... } 

我更喜歡第二個,因爲swap()通常不是類的接口的組成部分;它似乎更適合作爲一項免費功能。然而,這是品味的問題。

對於一個類型有一個優化的swap是實現C++ 0x中右值引用的一些好處的常用方法,所以如果類可以利用它並且你真的需要性能。

+3

這是潛在的危險!沒有明確的專門化,std :: swap需要一個工作拷貝賦值操作符,並且可以在其實現中使用它。這會使這個運算符=無限遞歸。 – 2009-10-07 20:09:00

+1

@Charles - 編輯,謝謝。我應該知道,因爲我曾犯過同樣的錯誤。 – 2009-10-07 20:37:17

+0

好的,但有人可能在不提供專門化的情況下使用std :: swap'或(gasp)'using namespace std'。如果要求類必須提供一個不直接使用賦值操作符的顯式交換函數(非靜態成員,靜態成員,自由函數或std :: swap完全特化)對於可交換對象,我通常會提供一個'Swap'成員函數,這樣它的拼寫就會不同,並且可以捕捉到忘記定義它的錯誤。我實現了一個'swap'自由函數和複製賦值運算符,它們都遵從'Swap'函數。 – 2009-10-07 21:45:38

0

我只能說這不好的形式,不是因爲你雙擊分配所有對象,但因爲在我的經驗,它往往是壞的形式上的默認拷貝構造函數/賦值運算符依靠一組特定的功能。由於這些信息不在任何地方,很難說你想要的行爲取決於他們的行爲。例如,如果一年中的某個人想要向你的班級添加一個字符串矢量,該怎麼辦?您不再擁有普通的舊數據類型,而維護人員很難知道他們正在破壞事物。

我認爲,從DRY的角度來看,從維護的角度來看,創建細微的未指定要求更加糟糕。即使重複自己,就像那樣糟糕,也不那麼邪惡。

1

就我個人而言,我認爲破碎的賦值運算符是殺手。我總是說,人們應該閱讀文檔,不要做任何告訴他們不要做的事情,但即使如此,寫作業也不需要考慮它,或者使用需要可分配類型的模板。有一個不可複製的習慣用法的原因:如果operator=不起作用,它是太危險,無法訪問。

如果我沒記錯的話,C++ 0x中都會讓你這樣做:

private: 
    A &operator=(const A &) = default; 

,至少可以說是唯一的類本身可以使用破壞默認賦值運算符,以及你希望在這個受限制的環境更容易小心。

0

我認爲更好的辦法是實現一個拷貝構造函數,如果該行爲是微不足道的(在你的情況下,它似乎被打破:至少分配和拷貝應該有類似的語義,但是你的代碼表明,這不會是這樣 - 但我想這是一個人爲的例子)。爲您生成的代碼不會錯。

如果您需要需要來實現這些方法,那麼很可能該類可以使用快速交換方法,從而能夠重新使用複製構造函數來實現賦值運算符。

如果你因爲某些原因需要提供一個默認的淺拷貝構造函數,那麼C++ 0X有

X(const X&) = default; 

但我不認爲這是奇怪的語義一個成語。在這種情況下,使用賦值而不是初始化很便宜(因爲未初始化的int不會花費任何東西),所以你可以像這樣做。

2

基本上,你所說的是你有你的班級的一些成員,這些成員對班級的身份沒有貢獻。就目前而言,您可以通過使用賦值運算符來複制類成員,然後重置那些不應該被複制的成員。這給你留下了一個與複製構造函數不一致的賦值運算符。

更好的辦法是使用複製和交換方式,並且表達哪些成員不應該複製到複製構造函數中。你仍然有一個表達「不復制這個成員」行爲的地方,但現在你的賦值操作符和拷貝構造函數是一致的。

class A 
{ 
public: 

    A() : a(), b(), c(), d() {} 

    A(const A& other) 
     : a(other.a) 
     , b(other.b) 
     , c() // c isn't copied! 
     , d(other.d) 

    A& operator=(const A& other) 
    { 
     A tmp(other); // doesn't copy other.c 
     swap(tmp); 
     return *this; 
    } 

    void Swap(A& other) 
    { 
     using std::swap; 
     swap(a, other.a); 
     swap(b, other.b); 
     swap(c, other.c); // see note 
     swap(d, other.d); 
    } 

private: 
    // ... 
}; 

注:swap成員函數,我已經換了c成員。爲了在賦值運算符中使用,保留了與複製構造函數匹配的行爲:它重新初始化c成員。如果您將swap函數公開,或通過swap免費函數提供對其的訪問,則應確保此行爲適用於交換的其他用途。

+0

運營商=應該以價值取得A - 我修改了我的答案,以便將其解決。此外,你忘了返回*這。 – 2009-10-08 16:12:38

+1

@Jeff Hardy:我不得不認爲這是一個風格問題。以價值取勝論有一些優點,但副本容易忽視。採用const引用使得副本更加明顯。在某些情況下,通過價值獲取可以實現一些額外的優化,但是我從來沒有看到它有明顯的不同。 – 2009-10-08 17:10:43

相關問題