2017-04-13 38 views
3

在C++ 11中,move-constructor/operator支持資源/內存移動。當使用移動構造函數時,將自身重置爲nullptr是否是一種好習慣?

這是我的例子:

class A { 
public: 
    A() : table_(nullptr), alloc_(0) {} 
    ~A() 
    { 
     if (table_) 
      delete[] table_; 
    } 

    A(const A & other) 
    { 
     // table_ is not initialized 
     // if (table_) 
     // delete[] table_; 
     table_ = new int[other.alloc_]; 
     memcpy(table_, other.table_, other.alloc_ * sizeof(int)); 
     alloc_ = other.alloc_; 
    } 
    A& operator=(const A & other) 
    { 
     if (table_) 
      delete[] table_; 
     table_ = new int[other.alloc_]; 
     memcpy(table_, other.table_, other.alloc_ * sizeof(int)); 
     alloc_ = other.alloc_; 
     return *this; 
    } 

    A(A && other) 
    { 
     // table_ is not initialized in constructor 
     // if (table_) 
     // delete[] table_; 
     table_ = other.table_; 
     alloc_ = other.alloc_; 
    } 

    A& operator=(A && other) 
    { 
     if (table_) 
      delete[] table_; 
     table_ = other.table_; 
     alloc_ = other.alloc_; 
    } 

private: 
    int *table_; 
    int alloc_; 
}; 

這似乎不錯,但有時我想搬到一個局部變量,就像這樣:

class B { 
private: 
    A a_; 

public: 
    void hello() 
    { 
     A tmp; 
     // do something to tmp 
     a_ = std::move(tmp); 
     // tmp.~A() is called, so a_ is invalid now. 
    } 
}; 

當函數結束時,tmp.~A()將被調用,此時,a_tmp指針有相同的table_,當tmp delete[] table_,a_'s table_將無效。

我在徘徊什麼時候應該使用std::move將tmp分配給a_,無需複製。

與答案的幫助下,我修改A的舉動,構造是這樣的:

class A { 
private: 
    void reset() 
    { 
     table_ = nullptr; 
     alloc_ = 0; 
    } 

public: 

    A(A && other) 
    { 
     table_ = other.table_; 
     alloc_ = other.alloc_; 
     other.reset(); 
    } 

    A& operator=(A && other) 
    { 
     std::swap(table_, other.table_); 
     std::swap(alloc_, other.alloc_); 
    } 
}; 

在這段代碼,當我移動的東西,我將交換新老參考,所以老tmp將刪除[]原來的a_table_,這是沒用的。

這是一個很好的習慣。

回答

4

當您從中的other移開時,還應將其移動的數據成員設置爲nulltpr。因此,固定的代碼應該如下所示:

A(A && other) 
{ 
    //if (table_) 
    // delete[] table_; // no need for this in move c-tor 
    table_ = other.table_; 
    other.table_ = nullptr; 
    alloc_ = other.alloc_; 
    other.alloc_ = nullptr; 
} 

A& operator=(A && other) 
{ 
    // as n.m. has pointed out, this move assignment does not 
    // protect against self assignment. One solution is to use 
    // swap aproach here. The other is to simply check if table_ == other.table_. 
    // Also see here for drawbacks of swap method: 
    // http://scottmeyers.blogspot.com/2014/06/the-drawbacks-of-implementing-move.html 
    delete[] table_; 
    table_ = other.table_; 
    other.table_ = nullptr; 
    alloc_ = other.alloc_; 
    other.alloc_ = nullptr; 
    return *this; 
} 

這使other在什麼標準要求valid but unspecified state

您還可以使用std ::交換如下:

A(A && other) 
{ 
    table_ = other.table_; 
    alloc_ = other.alloc_; 
} 

A& operator=(A && other) 
{ 
    std::swap(table_, other.table_); 
    std::swap(alloc_, other.alloc_); 
    return *this; 
} 

這種方式釋放將完成從何時對象移動被銷燬。

+0

是的,我更新了我的問題,我的解決方案與您的解決方案是一樣的,爲所有課程編寫這種'move'函數是一種好習慣嗎? – linrongbin

+0

@zhaochenyou是的,它的有效 – marcinj

+0

謝謝,哈哈,我很高興我找到了一個很好的解決方案。 – linrongbin

0

我認爲你需要改變你的轉移構造賦值運算符這樣的:

// Simple move constructor 
A(A&& arg) : member(std::move(arg.member)) // the expression "arg.member" is lvalue 
{} 
// Simple move assignment operator 
A& operator=(A&& other) { 
    member = std::move(other.member); 
    return *this; 
} 

定義here

1

您的移動構造函數和賦值操作符被有效執行淺拷貝。您應該將other.table設置爲nullptr,因爲這種情況下此舉有意義。當然,這將避免兩次刪除同一個數組中的未定義行爲,正如您在示例中所建議的那樣。

1

一個很好的選擇是交換移動構造函數中的值。

A& operator=(A && other) 
{ 
    using namespace std; 
    swap(table_, other.table_); 
    swap(alloc_, other.alloc_); 
    return *this; 
} 

這樣,源的內容放置在目標和後者的內容得到轉移到源 - 然後將正確地清理它們時被刪除本身(這是你所期望的,無論如何,否則,你不想移動物體......)。複製指針後

A(A&& other) : A() 
{ 
    *this = std::move(other); 
} 
+0

是的,'std :: swap'似乎比我的'reset' – linrongbin

+0

UB更好。一個'int'和一個'int *'不會自行初始化。 –

+0

@ n.m。仔細看看:我做構造函數轉發,調用默認構造函數! – Aconcagua

1

在移動構造函數/任務,爲它們分配「nullptr」,這樣,當析構函數被調用,這將是沒有:

移動的構造可以從上面分配的利潤則運。

這是我將如何去寫移動構造函數和賦值。此外,你可以避免「如果」檢查「刪除」,如果它是「nullptr」,它將是沒有操作。

A(A && other) 
    { 
     delete[] table_; 
     table_ = other.table_; 
     other.table_ = nullptr; 
     alloc_ = other.alloc_; 
    } 

    A& operator=(A && other) { 
     delete[] table_; 
     table_ = other.table_; 
     other.table_ = nullptr; // assign the source to be nullptr 
     alloc_ = other.alloc_; 
     return *this; 
    } 
2

有許多問題與此代碼(即使假設你想用數組和指針,你不應該在現實生活中,只要使用std :: vector的撥弄)。

錯誤代碼:

A() 
{ 
    table_ = nullptr; 
    alloc_ = 0; 
} 

不要在構造函數體使用分配,使用成員初始化名單。好代碼:

A() : table{nullptr}, alloc_ {0} {} 

對於其他構造函數也是如此。


冗餘代碼:

if (table_) 
     delete[] table_; 

delete是要再次檢查您的指針。 delete ing a nullptr是完全安全的。不要打擾。


非常糟糕的代碼:

A(const A & other) 
{ 
    if (table_) 
     delete[] table_; 

table未初始化。訪問它是UB。而且,不需要在構造函數中執行此檢查。新構建的對象中不會有任何分配。只需取消支票。其他構造函數也一樣。


錯誤代碼:

A& operator=(const A & other) 
{ 
    if (table_) 
     delete[] table_; 

不警惕自我分配。其他作業操作員也一樣。


這些都是不管你爲C++ 03還是C++ 11編寫代碼都需要學習的習慣。現在轉移:

A(A && other) 
{ 
    if (table_) 
     delete[] table_; 
    table_ = other.table_; 
    alloc_ = other.alloc_; 
} 

這是完全錯誤的。您需要更改從移動的對象,否則這根本不是一個動作,而是一個簡單的淺拷貝。

A(A && other) : table_{other.table_}, alloc_{other.alloc_} { 
{ 
    other.table_ = nullptr; 
    other.alloc_ = 0;   
} 

移動分配的同上。


中招構造函數std::swap是一個很好的習慣用法當你處理用戶定義的類型。原始類型並不完全需要它,主要是因爲您需要先初始化它們,然後立即進行交換,但您仍然可以使用它。

+0

現在我理解你的代碼了,是的,我同意你的話,我應該修改它們 – linrongbin

+0

其實我認爲你的評論是非常有幫助的,我會更新問題中的示例代碼。 – linrongbin

相關問題