2010-08-03 92 views
1

正如我在Move constructor/operator=中問過的那樣,經過一段時間,我已經同意並接受了正確的答案,我只是在想,如果將「移動析構函數」每次我們使用move ctor或operator =時,會在移動的對象上調用它。
通過這種方式,我們只需要在移動的時候指定我們想要的東西,以及在被移動構造函數使用後我們的對象是如何被廢棄的。如果沒有這種語義,它看起來像我每次寫移動ctor或operator =我必須明確說明他們每個人(代碼重複/錯誤介紹)如何取消移動的對象,這不是我認爲的最佳選擇。期待您對此主題的看法。Moving ctor and moving dtor

回答

2

你可以帶一個具體的例子,它會有用。 E.g,據我瞭解,此舉轉讓可能在一般情況下被作爲

this->swap(rhv); 

實現的交換方法在任何情況下可能是有益的,如果類受益於移動語義。這很好地代表了將*this的舊資源發佈到常規析構函數的工作。

如果沒有特別的例子顯示一種新的析構函數是實現正確代碼的一種優雅方式,那麼您的提議看起來並不會很吸引人。

此外,根據最新版本,移動構造函數/賦值運算符可以默認。這意味着,很可能 我的班會是這樣的:

class X 
{ 
    well_behaved_raii_objects; 
public: 
    X(X&&) = default; 
    X& operator=(X&&) = default; 
}; 

沒有析構函數在所有!什麼會讓它吸引我有兩個析構函數呢?

還要考慮到賦值運算符有舊資源來處理。按照當前的標準,你必須小心,正常的析構函數調用在構造和賦值以及IMO之後都是很好的,類似於提議的移動析構函數,你必須在構造函數和賦值運算符中注意相同的移動析構函數安全地叫。或者你想要兩個解析器 - 每個解析器一個?:)與移動構造函數/分配註釋的msdn example

#include <algorithm> 

class MemoryBlock 
{ 
public: 

    // Simple constructor that initializes the resource. 
    explicit MemoryBlock(size_t length) 
     : length(length) 
     , data(new int[length]) 
    { 
    } 

    // Destructor. 
    ~MemoryBlock() 
    { 
     delete[] data; //checking for NULL is NOT necessary 
    } 

    // Copy constructor. 
    MemoryBlock(const MemoryBlock& other) 
     : length(other.length) 
     , data(new int[other.length]) 
    { 
     std::copy(other.data, other.data + length, data); 
    } 

    // Copy assignment operator (replaced with copy and swap idiom) 
    MemoryBlock& operator=(MemoryBlock other) //1. copy resource 
    { 
     swap(other); //2. swap internals with the copy 
     return *this; //3. the copy's destructor releases our old resources 
    } 

    //Move constructor 
    //NB! C++0x also allows delegating constructors 
    //alternative implementation: 
    //delegate initialization to default constructor (if we had one), then swap with argument 
    MemoryBlock(MemoryBlock&& other) 
    : length(other.length) 
    , data(other.data) 
    { 
     other.data = 0; //now other can be safely destroyed 
     other.length = 0; //not really necessary, but let's be nice 
    } 

    MemoryBlock& operator=(MemoryBlock&& rhv) 
    { 
     swap(rhv); 
     //rhv now contains previous contents of *this, but we don't care: 
     //move assignment is supposed to "ruin" the right hand value anyway 
     //it doesn't matter how it is "ruined", as long as it is in a valid state 
     //not sure if self-assignment can happen here: if it turns out to be possible 
     //a check might be necessary, or a different idiom (move-and-swap?!) 
     return *this; 
    } 


    // Retrieves the length of the data resource. 
    size_t Length() const 
    { 
     return length; 
    } 

    //added swap method (used for assignment, but recommended for such classes anyway) 
    void swap(MemoryBlock& other) throw() //swapping a pointer and an int doesn't fail 
    { 
     std::swap(data, other.data); 
     std::swap(length, other.length); 
    } 

private: 
    size_t length; // The length of the resource. 
    int* data; // The resource. 
}; 

在原始樣品MSDN一些評論


返工例如:delete之前

1)檢查NULL是不必要(也許這裏是爲了我已經剝離的輸出而完成的,也許它表示有誤解)

2)刪除賦值運算符中的資源:代碼重複。使用複製和交換成語刪除以前持有的資源將委託給析構函數。

3)複製和交換習語也使得自我分配檢查不必要。如果在刪除之前將資源複製,則不會造成問題。 - (另一方面,「不管複製資源」只會在你希望用這個類完成大量自我分配時傷害)。

4)MSDN示例中的賦值運算符缺少任何類型的異常安全性:如果分配新存儲失敗,該類將處於無效狀態並帶有無效指針。在銷燬後,未定義的行爲將會發生。

這可以通過仔細地重新排序語句,並將刪除的指針設置爲NULL來改善(不幸的是,似乎這個特定類的不變量是它始終包含一個資源,所以讓它乾淨地丟失資源在例外情況下也不完美)。相比之下,通過複製和交換,如果發生異常,則左側值將保持原始狀態(好得多,操作無法完成,但數據丟失是可以避免的)。

5)自動分配檢查在移動賦值運算符中看起來特別有問題。我不明白左手價值如何與首要的右手價值相同。它需要a = std::move(a);來實現身份(看起來這將是未定義的行爲?)?

6)再次,移動任務是不必要的管理資源,我的版本只是委託給常規析構函數。

結論:你看到的代碼重複是可以避免的,它只是由一個天真的實現引入的(你出於某種原因傾向於在教程中看到這種實現,可能是因爲重複代碼更容易遵循學習者) 。

爲了防止資源泄漏,永久免費 資源(如內存,文件 句柄和套接字)中招 賦值運算符。

...如果代碼重疊很好,否則重用析構函數。

爲了防止不可恢復的破壞 資源,妥善處理 自賦值中招 賦值運算符。

...或者確保您不會刪除任何內容,然後才能確定可以替換它。 或者更確切地說是一個SO問題:在明確定義的程序中移動分配的情況下是否可能發生自我分配?


而且,從我的草案(3092),我發現,如果一個類沒有用戶定義拷貝構造函數/賦值運算符和無法阻止的舉動,構造函數/賦值的存在,一會隱含聲明如默認。如果我沒有弄錯,這意味着:如果成員是字符串,向量,shared_ptrs等,在這種情況下你通常不會寫複製構造函數/賦值,你會得到一個移動構造函數/移動賦值免費

+0

@叔叔可否請你附上一些鏈接,以便我可以閱讀它?我真的很感興趣。這是否意味着根據你所說的話,不再需要dtor了? – 2010-08-04 08:00:14

+0

你需要一個鏈接來說明class X {char * name; ...};'需要析構函數,而'類Y {字符串名稱; ...};'不? – UncleBens 2010-08-04 15:09:29

+0

@UncleBens不,我要求鏈接到一個材料,其中將解釋如何將默認應用於cpy ctor或工作分配optor。話雖如此,你在例子中說過的話意味着當然,只有當你不直接使用資源時,你才需要移動的Dtor。在任何其他情況下,使用_move析構函數_就像使用dtor一樣有用。 – 2010-08-04 15:58:25

0

有什麼不對的:

struct Object 
{ 
    Object(Object&&o) { ...move stuff...; nullify_object(o); } 
    Object & operator = (Object && o) { ...; nullify_object(o); } 

    void nullify_object(Object && o); 
}; 

或有nullify_object目標上調用的選擇:o.nullify();

我看到加入YANLF的沒有什麼大的好處。

+0

@Noah是的,但在你的情況下,你必須記得調用nullify_object。在我的命題中,移動析構函數將被隱含地調用。 – 2010-08-03 17:12:05

+0

@Noah如果您想澄清YANLF的含義,我將不勝感激。 – 2010-08-03 17:25:09

+0

又一個新的語言功能 – 2010-08-03 17:44:40

0

移動構造函數/賦值是您竊取資源並將被盜對象資源的狀態保持在允許在調用析構函數時安全銷燬對象的狀態的位置。除了移動構造函數/賦值之外,您無法查看或訪問您從中竊取資源的臨時「值」。

作爲一個例子,我們來看一個字符串。這就是你要從臨時對象中竊取分配的資源和大小的地方,你可以將它的值設置爲你自己的值(如果你的對象是默認構造的,它應該是null和0)。

+0

不能同意你的理由,如果你是誰設計課程,那麼你可以看到,你可以從你的課堂上獲得任何價值。至於你的字符串的例子,你不知道這個類的設計者如何處理移動的語義,但他們處理了這一點,他們看到了這個類的每一點。 – 2010-08-03 17:19:45