[英]Using copy constructor in assignment operator
在賦值運算符中使用copy-constructor是否違反樣式准則? 即:
const Obj & Obj::operator=(const Obj & source)
{
if (this == &source)
{
return *this;
}
// deep copy using copy-constructor
Obj * copy = new Obj(source);
// deallocate memory
this->~Obj();
// modify object
*this = *copy;
return *copy;
}
假定復制構造函數對對象執行深層復制。
編輯:
正如評論員指出的那樣,我的代碼非常錯誤。
至於總體概念性問題:正如WhozCraig所建議的那樣,復制/交換習語似乎是要走的路: 什么是復制與交換習語?
簡而言之,這是示例中的復制/交換習慣用法:
#include <algorithm>
class Obj
{
int *p;
void swap(Obj& left, Obj& right);
public:
Obj(int x = 0) : p(new int(x)) {}
Obj(const Obj& s);
Obj& operator = (const Obj& s);
~Obj() { delete p; }
};
Obj::Obj(const Obj& source) : p(new int(*source.p))
{}
void Obj::swap(Obj& left, Obj& right)
{
std::swap(left.p, right.p);
}
Obj & Obj::operator=(const Obj & source)
{
Obj temp(source);
swap(*this, temp);
return *this;
}
int main()
{
Obj o1(5);
Obj o2(o1);
Obj o3(10);
o1 = o3;
}
為了了解它是如何工作的,我特意創建了一個成員,該成員是指向動態分配的內存的指針(如果沒有用戶定義的副本構造函數和賦值運算符,這將是一個問題)。
如果您專注於賦值運算符,它將調用Obj
復制構造函數來構造一個臨時對象。 然后,調用特定於Obj
swap
,交換單個成員。 現在,調用swap
后魔術就在temp
對象中。
當析構函數temp
被調用時,它會調用delete
上的指針值this
曾經有過,但被換出與temp
指針。 因此,當temp
超出范圍時,它將清除“舊”指針分配的內存。
另外,還要注意分配過程中,如果new
拋出創建臨時對象的過程中的異常,分配將任何成員之前拋出異常this
成為改變。 這樣可以防止對象具有可能因意外更改而損壞的成員。
現在,給出了以前的答案,該答案使用了常用的“共享代碼”方法來進行復制分配。 這是此方法的完整示例,並解釋了其存在問題的原因:
class Obj
{
int *p;
void CopyMe(const Obj& source);
public:
Obj(int x = 0) : p(new int(x)) {}
Obj(const Obj& s);
Obj& operator = (const Obj& s);
~Obj() { delete p; }
};
void Obj::CopyMe(const Obj& source)
{
delete p;
p = new int(*source.p);
}
Obj::Obj(const Obj& source) : p(0)
{
CopyMe(source);
}
Obj & Obj::operator=(const Obj & source)
{
if ( this != &source )
CopyMe(source);
return *this;
}
所以你會說“這怎么了?” 好吧,這是錯誤的事情是, CopyMe
的第一件事是調用delete p;
。 然后,您將要問的下一個問題是:“那是什么?刪除舊的內存,這不是我們應該做的嗎?”
問題在於,隨后對new
調用可能會失敗。 因此,我們所做的就是在我們甚至不知道新數據可用之前就銷毀了數據。 如果現在new
拋出異常,則說明我們已經弄亂了對象。
是的,您可以通過創建臨時指針進行分配來輕松修復它,最后將temp指針分配給p
。 但是很多時候這是可以忘記的,即使上面的代碼有潛在的損壞錯誤,也永遠將其保留在代碼庫中。
為了說明,這里是CopyMe
的修復程序:
void Obj::CopyMe(const Obj& source)
{
int *pTemp = new int(*source.p);
delete p;
p = pTemp;
}
但是同樣,您將看到大量使用“共享代碼”方法的代碼,這些方法都有潛在的錯誤,並且沒有提及異常問題。
您嘗試執行的操作無效。 您似乎在想賦值運算符返回的對象將成為新的“ this”,但事實並非如此。 您尚未修改對象,只是銷毀了它。
Obj a;
Obj b;
a = b; // a has been destroyed
// and you've leaked a new Obj.
// At the end of the scope, it will try to destroy `a` again, which
// is undefined behavior.
可以通過使用放置new
來實現賦值運算符,但這是一個脆弱的解決方案,通常不建議這樣做:
const Obj & Obj::operator=(const Obj & source)
{
if (this == &source)
{
return *this;
}
this->~Obj();
new (this) Obj(source);
return *this;
}
如果在構造過程中可能發生異常,則將導致問題,並可能導致派生類出現問題。
在復制構造函數和assigmnet運算符之間共享代碼是完全有意義的,因為它們經常執行相同的操作(復制作為參數屬性傳遞給此對象的對象)。
從個性上講,我經常通過巧妙地編碼我的賦值運算符,然后從副本構造函數中調用它來做到這一點:
Obj::Obj(const Obj & source)
{
Obj::operator=( source );
}
const Obj& Obj::operator=(const Obj& source)
{
if (this != &source)
{
// copy source attribtes to this.
}
return *this;
}
如果您正確編寫了operator=
它將起作用。 如前所述,可能建議同時使用交換函數 : 復制構造函數和C ++中的=運算符重載:是否可以使用通用函數?
無論如何,您希望在兩個函數之間共享代碼的想法很好,但是實現它的方式卻不好。 可以肯定地工作..它有很多問題,沒有按照您的意思進行。 它遞歸地調用operator =。 而且,您永遠都不應像以前那樣顯式調用析構函數( this->~Obj();
〜Obj this->~Obj();
)。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.