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将已删除的指针,它超出范围,这意味着该指针将被删除两次之后将其删除.
我该如何避免这个问题?
三规则现在实际上是五规则。如果您有一个可以移动的类,您应该自己定义移动语义(加上复制、析构函数等)。
至于如何做到这一点,引用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)
您需要定义移动构造函数以防止从移动的对象中删除:
foo(foo&& f): i(f.i) {
f.i = nullptr;
}
Run Code Online (Sandbox Code Playgroud)
现在,当运行旧对象的析构函数时,它不会删除i,因为删除空指针是一个无操作.
您还应定义移动赋值运算符并删除复制赋值运算符.