2016-11-28 66 views
2

編輯:作出FooBar少一點瑣事,並直接替換shared_ptr<>更困難。應該使用unique_ptr來更輕鬆地實現「移動」語義?


應該​​可以作爲一個簡單的方法來實現移動語義?

對於一類像

class Foo 
{ 
    int* m_pInts; 
    bool usedNew; 
    // other members ... 

public: 
    Foo(size_t num, bool useNew=true) : usedNew(useNew) { 
     if (usedNew) 
      m_pInts = new int[num]; 
     else 
      m_pInts = static_cast<int*>(calloc(num, sizeof(int))); 
    } 
    ~Foo() { 
     if (usedNew) 
      delete[] m_pInts; 
     else 
      free(m_pInts); 
    } 

    // no copy, but move 
    Foo(const Foo&) = delete; 
    Foo& operator=(const Foo&) = delete; 
    Foo(Foo&& other) { 
     *this = std::move(other); 
    } 
    Foo& operator=(Foo&& other) { 
     m_pInts = other.m_pInts; 
     other.m_pInts = nullptr; 
     usedNew = other.usedNew; 
     return *this; 
    } 
}; 

實施舉動變得數據成員被添加比較繁瑣。但是,可移動數據可以放置在單獨的struct中,其實例由​​管理。這使得=default用於招:

class Bar 
{ 
    struct Data 
    { 
     int* m_pInts; 
     bool usedNew; 
     // other members ... 
    }; 
    std::unique_ptr<Data> m_pData = std::make_unique<Data>(); 

public: 
    Bar(size_t num, bool useNew = true) { 
     m_pData->usedNew = useNew; 
     if (m_pData->usedNew) 
      m_pData->usedNew = new int[num]; 
     else 
      m_pData->m_pInts = static_cast<int*>(calloc(num, sizeof(int))); 
    } 
    ~Bar() { 
     if (m_pData->usedNew) 
      delete[] m_pData->m_pInts; 
     else 
      free(m_pData->m_pInts); 
    } 

    // no copy, but move 
    Bar(const Bar&) = delete; 
    Bar& operator=(const Bar&) = delete; 
    Bar(Bar&& other) = default; 
    Bar& operator=(Bar&& other) = default; 
}; 

除了內存爲​​實例始終是在堆中,還有什麼其他問題,像這樣的實現存在嗎?

+0

請'的std :: unique_ptr'應該使用,當且僅當資源的所有權是獨一無二的。在std :: unique_ptr上移動語義僅用於轉移所有權。 https://github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md –

+0

@MathieuVanNevel是的,我打算通過'm_pInts'的操縱來轉移所有權。 –

+0

什麼史前'calloc'? – SergeyA

回答

1

我的建議是單獨關注使用的組合物

管理分配內存的生存期是智能指針的工作。如何將該內存(或其他資源)返回到運行時是智能指針刪除器的關注點。

一般來說,如果你發現自己寫移動操作符並移動構造函數,那是因爲你沒有充分分解這個問題。

例子:

#include <cstring> 
#include <memory> 

// a deleter 
// 
struct delete_or_free 
{ 
    void operator()(int* p) const 
    { 
     if (free_) { 
     std::free(p); 
    } 
     else { 
     delete [] p; 
     } 
    } 

    bool free_; 
}; 


class Foo 
{ 
    // 
    // express our memory ownership in terms of a smart pointer. 
    // 
    using ptr_type = std::unique_ptr<int[], delete_or_free>; 
    ptr_type ptr_; 

    // other members ... 

    // 
    // some static helpers (reduces clutter in the constructor) 
    // 
    static auto generate_new(int size) { 
    return ptr_type { new int[size], delete_or_free { false } }; 
    } 

    static auto generate_calloc(int size) { 
    return ptr_type { 
     static_cast<int*>(calloc(size, sizeof(int))), 
     delete_or_free { true } 
    }; 
    } 

public: 

    // 
    // our one and only constructor 
    // 
    Foo(size_t num, bool useNew=true) 
     : ptr_ { useNew ? generate_new(num) : generate_calloc(num) } 
    { 
    } 

    // it's good manners to provide a swap, but not necessary. 
    void swap(Foo& other) noexcept { 
     ptr_.swap(other.ptr_); 
    } 
}; 

// 
// test 
// 
int main() 
{ 
    auto a = Foo(100, true); 
    auto b = Foo(200, false); 

    auto c = std::move(a); 
    a = std::move(b); 
    b = std::move(c); 

    std::swap(a, b); 
} 
+0

部分是處理遺留代碼,因此它可能不那麼容易「充分分解[d]的問題。」 我不喜歡你對move構造函數/運營商一般建議。 –

+1

@Dan我想你只需要在你允許編輯遺留代碼的地方插入智能指針。 我認爲您知道使用定製刪除程序的智能指針可以處理釋放任何類型的遺留資源嗎?例如文件,套接字,捲曲句柄,SQL句柄等? –

+0

我喜歡這個答案,因爲「如果傷害,不要這樣做」的方法。 :-) –

4

這被稱爲零規則。

零規則表示大多數類不執行復制/移動任務/構造或銷燬。相反,您將其委託給資源處理類。

5的規則規定,如果您實施5複製/移動分配/託管或託管中的任何一個,則應實施或刪除其中的5個(或者在適當考慮之後,默認它們)。

在你的情況下,m_pInts應該是唯一的指針,而不是一個原始內存處理緩衝區。如果它與某個東西(比如說大小)有關係,那麼你應該寫一個實現5的規則的指針和大小的結構。或者如果3個指針而不是2的開銷是可以接受的,你就使用一個std::vector<int>

部分原因是您停止直接調用newnew是直接管理資源的5規則類型中的實現細節。業務邏輯類不要與new混淆。他們既不新,也不刪除。

unique_ptr僅僅是資源管理的一種類別之一。 std::string,std::vector,std::set,shared_ptr,std::future,std::function - 大多數C++ std類型有資格。寫你自己也是一個好主意。但是,當你這樣做時,你應該從「業務邏輯」中去除資源代碼。

所以,如果你寫了一個std::function<R(Args...)>克隆,你可以使用unique_ptrboost::value_ptr來存儲函數對象內部的膽量。也許你甚至會寫一個sbo_value_ptr,它有時存在於堆上,有時也存在於本地。

那麼你還是換,隨着std::function那瞭解到,被指向事物的「商業邏輯」,以是可調用等。

的「商業邏輯」 std::function將不執行復制/移動分配/構造函數,也沒有析構函數。它們可能明確地爲=default

+0

假設'Foo'是一個傳統類,我想快速地將它移動; Bar是合理的方法嗎?或者我應該按照你的建議故意對'Foo'進行更實質性的修改,例如將'm_pInts'改爲'unique_ptr'? –

+0

@丹製作'm_pInts'一個'unique_ptr'也不是很可觀的,除非它是使用次數100S在你的代碼庫,其中內容被移入和移出在搖搖欲墜的方式公共成員,有時擁有它的內容,有時它不會。但是,如果是這樣的話,你所做的任何事都不會容易,甚至連'Bar'計劃都不會。 – Yakk

+0

...好的......'m_pInts'實際上只是會員數據的佔位符,需要一個不平凡的舉動。也許我需要在一個稍微更具體的例子工作...動機的 –

6

是的。你要找的是所謂的零規則(作爲三/五規則的C++ 11擴展)。通過讓您的數據都知道如何複製和移動自己,外部類不需要編寫任何特殊成員函數。編寫這些特殊成員可能容易出錯,因此不必編寫它們可以解決很多問題。

所以Foo只會變成:

class Foo 
{ 
    std::unique_ptr<size_t[]> data; 

public: 
    Foo(size_t size): data(new size_t[size]) { } 
}; 

,這就是很容易證明的正確性。

相關問題