[英]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.