【问题标题】:How to correctly manage resources with move semantics?如何使用移动语义正确管理资源?
【发布时间】:2016-03-10 03:07:33
【问题描述】:
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));
}

这很糟糕,因为似乎f 的析构函数一旦移入shared_ptr 就会被调用。 shared_ptr 会有一个已删除的指针,超出作用域后会删除,也就是说指针会被删除两次。

如何避免这个问题?

【问题讨论】:

  • 不要默认移动构造函数?
  • 是的,实际上定义了移动 ctor 和交换指针,例如或者干脆使用已经为它定义了移动语义的东西,比如std::unique_ptr
  • 而不是原始数组,只需使用std::vector。那里。一切都好。在 C++ 中管理资源的关键是使用标准资源管理器。至少尽可能。
  • @Cheersandhth.-Alf 我在这里看不到原始数组。
  • 在这种情况下,您可以只使用unique_ptr&lt;int&gt; i 和零规则。如果资源需要其他发布代码,您还可以将unique_ptr 与自定义删除器一起使用。避免重新发明轮子是件好事!

标签: c++ move c++14 move-semantics


【解决方案1】:

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

foo(foo&& f): i(f.i) {
  f.i = nullptr;
}

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

您还应该定义一个移动赋值运算符并删除复制赋值运算符。

【讨论】:

  • 如果我的用例不是那么简单怎么办?如果我需要调用 delete_something(some_handle) 并且某些默认值没有“noop”并且调用它两次会出现问题怎么办?
  • 你总是可以回退到保留一个布尔标志来标记是否清理资源。
  • @MaikKlein 我不太明白。您能否提供一个更详细的示例,可能在原始问题中?
  • class foo 可以有一个成员 bool shouldDestroy 将在销毁时检查。如果是true,则销毁资源。
【解决方案2】:

rule of three 现在真的是五法则。如果你有一个可以移动的类,你应该自己定义移动语义(加上副本、析构函数等)。

至于如何做到这一点,引用cppreference's page on 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
    }
};

【讨论】:

  • 不用三行交换,可以写std::swap(resource, rhs.resource);
猜你喜欢
  • 2014-06-02
  • 2019-02-17
  • 2017-06-21
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-11-10
  • 1970-01-01
相关资源
最近更新 更多