如何使用移动语义正确管理资源?
How to correctly manage resources with move semantics?
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
会有一个被删除的指针,超出作用域后会被删除,也就是说这个指针会被删除两次。
如何避免这个问题?
您需要定义移动构造函数以防止从移出对象中删除:
foo(foo&& f): i(f.i) {
f.i = nullptr;
}
现在当旧对象的析构函数是 运行 时,它不会删除 i
,因为删除空指针是一个空指针。
您还应该定义移动赋值运算符并删除复制赋值运算符。
现在rule of three真的是五人制。如果你有一个可以移动的class,你应该自己定义移动语义(加上复制、析构函数等)。
至于如何做到这一点,引用 cppreference's page on std::move
、“...已移动的对象处于有效但未指定的状态。”未指定状态通常是对象在默认初始化时的样子,或者如果对象 swap
调用它们会发生什么。
正如@zenith 回答的那样,一种直接的方法是让移动构造函数(或赋值运算符)将原始指针设置为 nullptr
。这样数据就不会被释放,原始对象仍然处于有效状态。
如前所述,另一个常见的习语是使用swap
。如果 class 需要自己的复制和移动语义,那么 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
}
};
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
会有一个被删除的指针,超出作用域后会被删除,也就是说这个指针会被删除两次。
如何避免这个问题?
您需要定义移动构造函数以防止从移出对象中删除:
foo(foo&& f): i(f.i) {
f.i = nullptr;
}
现在当旧对象的析构函数是 运行 时,它不会删除 i
,因为删除空指针是一个空指针。
您还应该定义移动赋值运算符并删除复制赋值运算符。
现在rule of three真的是五人制。如果你有一个可以移动的class,你应该自己定义移动语义(加上复制、析构函数等)。
至于如何做到这一点,引用 cppreference's page on std::move
、“...已移动的对象处于有效但未指定的状态。”未指定状态通常是对象在默认初始化时的样子,或者如果对象 swap
调用它们会发生什么。
正如@zenith 回答的那样,一种直接的方法是让移动构造函数(或赋值运算符)将原始指针设置为 nullptr
。这样数据就不会被释放,原始对象仍然处于有效状态。
如前所述,另一个常见的习语是使用swap
。如果 class 需要自己的复制和移动语义,那么 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
}
};