簡體   English   中英

Copy-and-Swap Idiom應該成為C ++ 11中的復制和移動習慣嗎?

[英]Should the Copy-and-Swap Idiom become the Copy-and-Move Idiom in C++11?

本回答所述,復制和交換習慣用法如下實現:

class MyClass
{
private:
    BigClass data;
    UnmovableClass *dataPtr;

public:
    MyClass()
      : data(), dataPtr(new UnmovableClass) { }
    MyClass(const MyClass& other)
      : data(other.data), dataPtr(new UnmovableClass(*other.dataPtr)) { }
    MyClass(MyClass&& other)
      : data(std::move(other.data)), dataPtr(other.dataPtr)
    { other.dataPtr= nullptr; }

    ~MyClass() { delete dataPtr; }

    friend void swap(MyClass& first, MyClass& second)
    {
        using std::swap;
        swap(first.data, other.data);
        swap(first.dataPtr, other.dataPtr);
    }

    MyClass& operator=(MyClass other)
    {
        swap(*this, other);
        return *this;
    }
};

通過將MyClass的值作為operator =的參數,可以通過復制構造函數或移動構造函數構造參數。 然后,您可以安全地從參數中提取數據。 這可以防止代碼重復並有助於異常安全。

答案提到您可以在臨時中交換或移動變量。 它主要討論交換。 但是,交換(如果未由編譯器優化)涉及三個移動操作,而在更復雜的情況下,還需要額外的額外工作。 當你想要的時候,就是臨時文件移動到assign-to對象中。

考慮這個更復雜的例子,涉及觀察者模式 在這個例子中,我手動編寫了賦值運算符代碼。 重點是移動構造函數,賦值運算符和交換方法:

class MyClass : Observable::IObserver
{
private:
    std::shared_ptr<Observable> observable;

public:
    MyClass(std::shared_ptr<Observable> observable) : observable(observable){ observable->registerObserver(*this); }
    MyClass(const MyClass& other) : observable(other.observable) { observable.registerObserver(*this); }
    ~MyClass() { if(observable != nullptr) { observable->unregisterObserver(*this); }}

    MyClass(MyClass&& other) : observable(std::move(other.observable))
    {
        observable->unregisterObserver(other);
        other.observable.reset(nullptr);
        observable->registerObserver(*this);
    }

    friend void swap(MyClass& first, MyClass& second)
    {
        //Checks for nullptr and same observable omitted
            using std::swap;
            swap(first.observable, second.observable);

            second.observable->unregisterObserver(first);
            first.observable->registerObserver(first);
            first.observable->unregisterObserver(second);
            second.observable->registerObserver(second);
    }

    MyClass& operator=(MyClass other)
    {
        observable->unregisterObserver(*this);
        observable = std::move(other.observable);

        observable->unregisterObserver(other);
        other.observable.reset(nullptr);
        observable->registerObserver(*this);
    }
}

顯然,此手動編寫的賦值運算符中的代碼的重復部分與移動構造函數的相同。 您可以在賦值運算符中執行交換,行為也是正確的,但它可能執行更多移動並執行額外的注冊(在交換中)和取消注冊(在析構函數中)。

重新使用移動構造函數的代碼不是更有意義嗎?

private:
    void performMoveActions(MyClass&& other)
    {
        observable->unregisterObserver(other);
        other.observable.reset(nullptr);
        observable->registerObserver(*this);
    }

public:
    MyClass(MyClass&& other) : observable(std::move(other.observable))
    {
        performMoveActions(other);
    }

    MyClass& operator=(MyClass other)
    {
        observable->unregisterObserver(*this);
        observable = std::move(other.observable);

        performMoveActions(other);
    }

在我看來,這種方法從不遜色於交換方法。 我是否正確地認為復制和交換習慣用作C ++ 11中的復制和移動習慣用法會更好,或者我是否錯過了重要的東西?

首先,只要您的類是可移動的,通常不必在C ++ 11中編寫swap函數。 默認swap將采取移動:

void swap(T& left, T& right) {
    T tmp(std::move(left));
    left = std::move(right);
    right = std::move(tmp);
}

就是這樣,元素被交換了。

其次,基於此,Copy-And-Swap實際上仍然存在:

T& T::operator=(T const& left) {
    using std::swap;
    T tmp(left);
    swap(*this, tmp);
    return *this;
}

// Let's not forget the move-assignment operator to power down the swap.
T& T::operator=(T&&) = default;

將復制和交換(這是一個移動)或移動和交換(這是一個移動),並應始終達到接近最佳性能。 可能有一些冗余分配,但希望您的編譯器會處理它。

編輯:這只實現了復制賦值運算符; 還需要一個單獨的移動賦值運算符,盡管它可以是默認的,否則會發生堆棧溢出(移動賦值和交換無限期地相互調用)。

給每個特殊成員提供應有的溫柔關懷,並盡可能地將其默認:

class MyClass
{
private:
    BigClass data;
    std::unique_ptr<UnmovableClass> dataPtr;

public:
    MyClass() = default;
    ~MyClass() = default;
    MyClass(const MyClass& other)
        : data(other.data)
        , dataPtr(other.dataPtr ? new UnmovableClass(*other.dataPtr)
                                : nullptr)
        { }
    MyClass& operator=(const MyClass& other)
    {
        if (this != &other)
        {
            data = other.data;
            dataPtr.reset(other.dataPtr ? new UnmovableClass(*other.dataPtr)
                                        : nullptr);
        }
        return *this;
    }
    MyClass(MyClass&&) = default;
    MyClass& operator=(MyClass&&) = default;

    friend void swap(MyClass& first, MyClass& second)
    {
        using std::swap;
        swap(first.data, second.data);
        swap(first.dataPtr, second.dataPtr);
    }
};

如果需要,析構函數可以在上面隱式默認。 對於此示例,其他所有內容都需要明確定義或默認。

參考: http//accu.org/content/conf2014/Howard_Hinnant_Accu_2014.pdf

復制/交換習慣用語可能會讓您失去性能(請參閱幻燈片)。 例如,有沒有想過為什么高性能/經常使用的std ::類型如std::vectorstd::string不使用copy / swap? 表現不佳是原因。 如果BigClass包含任何std::vector s或std::string s(似乎很可能),那么最好的辦法就是從你的特殊成員中調用他們的特殊成員。 以上是如何做到這一點。

如果您在作業時需要強大的異常安全性,請參閱幻燈片,了解除性能之外的其他方法(搜索“strong_assign”)。

我問這個問題已經有很長一段時間了,我現在已經知道答案了一段時間,但我已經推遲了為它寫答案。 這里是。

答案是不。 復制和交換習語不應該成為復制和移動的習語。

Copy-and-swap(也是Move-construct-and-swap)的一個重要部分是一種通過安全清理實現賦值運算符的方法。 舊數據被交換為復制構造或移動構造的臨時。 操作完成后,臨時刪除,並調用其析構函數。

交換行為可以重用析構函數,因此您不必在賦值運算符中編寫任何清理代碼。

如果沒有要執行的清理行為且只有賦值,那么您應該能夠將賦值運算符聲明為默認值,並且不需要copy-and-swap。

移動構造函數本身通常不需要任何清理行為,因為它是一個新對象。 一般簡單的方法是使移動構造函數調用默認構造函數,然后將所有成員與move-from對象交換。 然后,移動的對象將像一個平淡的默認構造對象。

但是,在這個問題的觀察者模式示例中,這實際上是一個異常,您必須進行額外的清理工作,因為需要更改對舊對象的引用。 一般來說,我建議盡可能使觀察者和觀察者以及基於參考的其他設計結構不可移動。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

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