关于移动赋值和移动构造你可能需要注意的地方 #248
Replies: 5 comments 21 replies
-
我觉得你不如顺便放点 |
Beta Was this translation helpful? Give feedback.
-
msvc STL example:
template <class _Dx2 = _Dx, enable_if_t<is_move_assignable_v<_Dx2>, int> = 0>
_CONSTEXPR23 unique_ptr& operator=(unique_ptr&& _Right) noexcept {
reset(_Right.release());
_Mypair._Get_first() = _STD forward<_Dx>(_Right._Mypair._Get_first());
return *this;
}
shared_ptr& operator=(shared_ptr&& _Right) noexcept { // take resource from _Right
shared_ptr(_STD move(_Right)).swap(*this);
return *this;
} |
Beta Was this translation helpful? Give feedback.
-
@Mq-b 这么有价值的坑不水个视频? |
Beta Was this translation helpful? Give feedback.
-
作为和标准库有较深入接触的人,我得说楼主的策略在标准库中几乎没有得到采用。标准库实现中像 (只)推荐 如果要用现成的函数,我的推荐是用 struct A {
A() = default; // 如果允许对象的状态,为什么不让默认构造函数创建它?
~A() { delete ptr; }
A(A&& right) noexcept :
ptr(std::exchange(right.ptr, nullptr /* 或者空列表 {} */})) {}
A& operator=(A&& right) noexcept // 返回 void 小心 std::assignable_from 找你麻烦。
{
delete std::exchange(ptr, std::exchange(right.ptr, nullptr)); // 无论 *this 和 right 是否为同一对象都 OK!
return *this;
}
private:
char* ptr{};
}; 这个做法的好处是能轻松地处理自移动赋值。 通常情况下
显然没有问题。 然而,如果
同样没有问题!而且保证最后 |
Beta Was this translation helpful? Give feedback.
-
此文为对 #206 的补充,先说结论:
当然部分朋友认为这是常识,但这确实是实际开发中可能遇到的错误之一。
我们在成员中持有指针时, 会直接在移动实现中选择 ptr = right.ptr, 这样往往会埋下一个暗雷 :
上面的代码就是一个错误的示范,尽管已经通过
right.ptr = nullptr
进行置空避免了资源被意外释放,但仍旧造成了内存泄漏。移动构造函数 或 移动赋值函数 的目的是为了直接
转移右值所持有资源的所有权
,而非拷贝。这里有个不易察觉的点,
转移右值所持有资源的所有权
并不意味着标准保证用户不需要对左值原本持有的资源
负责。可以选择手动追加一句
delete ptr;
但这样做无疑是繁琐的,尤其是类似的成员存在复数个的情况。应优先选择使用std::swap
,把 左值 的旧资源 托管给 右值 的析构函数进行释放,语义也更加明确。事实上,实际开发中,你需要
尽可能少的主动控制过程
,转而根据需求合理选择封装好的、安全的接口。越是做没必要的工作,你就越是要承担更多原本不必要的责任。一个相似的例子是,同事在代码中大量使用了C风格的内存申请与size记录且并未进行合理封装(实际情况要比我描述的抽象得多),然后每次重新分配或使用内存就不得不手动确保自己每一处代码正确的同时更新了 ptr 和 size,你能想到的,他因此遇到了许多原本可以轻松避免的问题。
移动的实现也是如此,你往往不需要手动的释放左值原有的资源,使用
std::swap
就足够了。Beta Was this translation helpful? Give feedback.
All reactions