使用 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)
}
这可能与此有关,对吧?
Double delete in 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 错误。但是看起来您的代码虽然格式正确,但可能也没有按照您的要求执行(但是只有您可以肯定地说)。
我在你的 Foo
:
中添加了打印语句
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
添加了 identity/state,以便更容易看到发生了什么。
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)
{}
所以总而言之,我认为编译器和当前代码都因错误而获奖。尽管从评论中可以看出,当前的代码错误只是正确减少代码以隔离问题的产物。
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
-取消引用错误。如果 other
处于移出状态,则 ->clone()
将取消引用 nullptr
。从技术上讲,如果您非常非常小心,就可以摆脱这种情况。但是你很容易不小心碰到这个错误。
以下代码会在 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)
}
这可能与此有关,对吧?
Double delete in 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 错误。但是看起来您的代码虽然格式正确,但可能也没有按照您的要求执行(但是只有您可以肯定地说)。
我在你的 Foo
:
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
添加了 identity/state,以便更容易看到发生了什么。
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)
{}
所以总而言之,我认为编译器和当前代码都因错误而获奖。尽管从评论中可以看出,当前的代码错误只是正确减少代码以隔离问题的产物。
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
-取消引用错误。如果 other
处于移出状态,则 ->clone()
将取消引用 nullptr
。从技术上讲,如果您非常非常小心,就可以摆脱这种情况。但是你很容易不小心碰到这个错误。