如何使用移动语义正确管理资源?

Mai*_*ein 1 c++ move move-semantics c++14

struct foo{
  int* i;
  foo(): i(new int(42)){}
  foo(const foo&) = delete;
  foo(foo&&) = default;
  ~foo(){
    std::cout << "destructor: i" << std::endl;
    delete(i);
  }
};
int main()
{
  foo f;
  auto sp_f = std::make_shared<foo>(std::move(f));
}
Run Code Online (Sandbox Code Playgroud)

这很糟糕,因为似乎析构函数f一旦进入就会被调用shared_ptr.在shared_ptr将已删除的指针,它超出范围,这意味着该指针将被删除两次之后将其删除.

我该如何避免这个问题?

Wea*_*ish 6

三规则现在实际上是五规则。如果您有一个可以移动的类,您应该自己定义移动语义(加上复制、析构函数等)。

至于如何做到这一点,引用cppreference 的页面std::move“...已移动的对象被置于有效但未指定的状态。” 未指定的状态通常是对象默认初始化时的样子,或者对象调用swap它们时会发生的情况。

正如 @zenith 所回答的,一种简单的方法是让移动构造函数(或赋值运算符)将原始指针设置为nullptr。这样数据就不会被释放,并且原始对象仍然处于有效状态。

如前所述,另一个常见的习惯用法是使用swap. 如果一个类需要自己的复制和移动语义,swap那么方法也会很方便。移动构造函数会将初始化委托给默认构造函数,然后调用该swap方法。在移动赋值运算符中,只需调用swap. 被移入的对象将获得资源,而另一个对象的析构函数将释放原始资源。

它通常看起来像这样:

struct Foo
{
    void* resource; //managed resource
    Foo() : resource(nullptr) {} //default construct with NULL resource
    Foo(Foo&& rhs) : Foo() //set to default value initially
    {
        this->swap(rhs); //now this has ownership, rhs has NULL
    }
    ~Foo()
    {
        delete resource;
    }
    Foo& operator= (Foo&& rhs)
    {
        this->swap(rhs); //this has ownership, rhs has previous resource
    }
    void swap(Foo& rhs) //basic swap operation
    {
        std::swap(resource, rhs.resource); //thanks @M.M
    }
};
Run Code Online (Sandbox Code Playgroud)


eml*_*lai 5

您需要定义移动构造函数以防止从移动的对象中删除:

foo(foo&& f): i(f.i) {
  f.i = nullptr;
}
Run Code Online (Sandbox Code Playgroud)

现在,当运行旧对象的析构函数时,它不会删除i,因为删除空指针是一个无操作.

您还应定义移动赋值运算符并删除复制赋值运算符.