繁体   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