簡體   English   中英

在賦值運算符中使用復制構造函數

[英]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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM