簡體   English   中英

使用unique_ptr成員編寫移動構造函數的正確方法(崩潰)

[英]Correct way to write move constructor with unique_ptr member (crash)

以下代碼將在Visual Studio 2013下崩潰

我想知道為什么:在這種情況下編寫移動構造函數的正確方法是什么? 刪除移動構造函數可以解決問題。 這是VC ++的錯誤還是這個代碼錯了?

移動構造函數的默認定義會有什么不同,使得這些代碼不會崩潰,而我自己的定義呢?

#include <memory>
#include <vector>

class A
{};

class Foo
{
public:
    Foo(std::unique_ptr<A> ref) : mRef(std::move(ref)) {}
    Foo(Foo&& other) : mRef(std::move(other.mRef)) {}

    Foo(const Foo& other) {}
    Foo& operator=(const Foo& other) { return *this; }

protected:
    std::unique_ptr<A> mRef;
};

int main(int argc, char *argv[])
{
    std::vector<Foo>({ Foo(std::make_unique<A>()), Foo(std::make_unique<A>()) });
    // Crash : Debug Assertion Failed !
    // Expression : _BLOCK_TYPE_IS_VALID(pHead->nBlockUse)
}

它可能與此有關,對吧?

在initializer_list vs 2013中雙重刪除

這是帶有充實復制構造函數和賦值的實際代碼,但錯誤完全相同

class A
{
public:
     std::unique_ptr<A> clone() { return std::make_unique<A>(*this); }
};

class Foo
{
public:
    Foo(std::unique_ptr<A> ref) : mRef(std::move(ref)) {}
    Foo(Foo&& other) : mRef(std::move(other.mRef)) {}

    Foo(const Foo& other) : mRef(other.mRef->clone()) {}
    Foo& operator=(const Foo& other) { mRef = other.mRef->clone(); return *this; }

protected:
    std::unique_ptr<A> mRef;
};

這聽起來像VS-2013的bug。 但是看起來你的代碼雖然形式很好,但可能也沒有做到你想要的東西(但是只有你可以這么說)。

我在你的Foo添加了一個print語句:

class Foo
{
public:
    Foo(std::unique_ptr<A> ref) : mRef(std::move(ref)) {}
    Foo(Foo&& other) : mRef(std::move(other.mRef)) {}

    Foo(const Foo& other) {}
    Foo& operator=(const Foo& other) { return *this; }

    friend std::ostream&
    operator<<(std::ostream& os, const Foo& f)
    {
        if (f.mRef)
            os << *f.mRef;
        else
            os << "nullptr";
        return os;
    }

protected:
    std::unique_ptr<A> mRef;
};

我還在你的main添加了一個打印聲明:


除此之外:我還為你的A添加了身份/狀態,以便更容易看到發生了什么。


int main(int argc, char *argv[])
{
    std::vector<Foo> v({ Foo(std::make_unique<A>(1)), Foo(std::make_unique<A>(2)) });
    // Crash : Debug Assertion Failed !
    // Expression : _BLOCK_TYPE_IS_VALID(pHead->nBlockUse)
    for (const auto& p : v)
        std::cout << p << '\n';
}

對我來說這個輸出:

nullptr
nullptr

我相信這是正確的輸出。

無法initializer_list移動,因此vector構造函數調用Foo的復制構造函數,它只是默認構造unique_ptr

實際上,如果刪除了應該隱式刪除的Foo拷貝構造函數(或者你可以顯式刪除它),程序就不應該編譯。

要真正做到這一點,你必須給Foo一個可操作的拷貝構造函數。 也許是這樣的:

    Foo(const Foo& other)
        : mRef(other.mRef ? new A(*other.mRef) : nullptr)
    {}

總而言之,我認為編譯器和當前代碼都會獲得bug的獎勵。 雖然從評論中可以看出,當前的代碼錯誤只是正確減少代碼以隔離問題的工件。

VS-2013錯誤。

至於你的移動構造函數,它很好。 雖然如果使用= default實現它會更好。 如果這樣做,它將自動繼承noexcept規范。 這樣的規范不應掉以輕心。 有效使用vector<Foo>是最重要的。

我的理解是VS-2013既不理解默認的移動成員也不理解noexcept

我對VS-2013的軼事經驗是,一個人經歷的編譯器錯誤數量與花括號的使用成正比。 我希望VS-2015能夠與這種體驗相悖。 與此同時,我建議避免使用涉及{}構造表達式。


更新

您更新的副本成員:

class Foo
{
public:
    Foo(std::unique_ptr<A> ref) : mRef(std::move(ref)) {}
    Foo(Foo&& other) : mRef(std::move(other.mRef)) {}

    Foo(const Foo& other) : mRef(other.mRef->clone()) {}
    Foo& operator=(const Foo& other) { mRef = other.mRef->clone(); return *this; }

protected:
    std::unique_ptr<A> mRef;
};

潛在的 nullptr -dereference錯誤。 如果other處於移動狀態,則->clone()將取消引用nullptr 從技術上講,如果你非常非常小心的話,你可以僥幸逃脫。 但是你很容易意外地碰到這個bug。

暫無
暫無

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

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