通过在移动赋值运算符中使用std::swap来重用析构函数逻辑有意义吗

Does it make sense to reuse destructor logic by using std::swap in a move assignment operator?

本文关键字:析构函数 有意义 swap std 移动 赋值运算符      更新时间:2023-10-16

考虑以下内容:

class Example : boost::noncopyable
{
    HANDLE hExample;
public:
    Example()
    {
        hExample = InitializeHandle();
    }
    ~Example()
    {
        if (hExample == INVALID_HANDLE_VALUE)
        {
            return;
        }
        FreeHandle(hExample);
    }
    Example(Example && other)
        : hExample(other.hExample)
    {
        other.hExample = INVALID_HANDLE_VALUE;
    }
    Example& operator=(Example &&other)
    {
        std::swap(hExample, other.hExample); //?
        return *this;
    }
};

我的想法是,析构函数很快就会在"其他"上运行,因此我不必通过使用swap在move赋值运算符中再次实现析构函数逻辑。但我不确定这是一个合理的假设。这样可以吗?

想象一下:

// global variables
Example foo;
struct bar {
    void f() {
        x = std::move(foo); // the old x will now live forever
    }
    Example x;
}

类似的习惯用法,复制和交换(或者在本例中,移动和交换),可以确保析构函数立即运行,我认为这是一个更好的语义。

Example& operator=(Example other) // other will be moved here
{
    std::swap(hExample, other.hExample);
    return *this;
} // and destroyed here, after swapping

应该可以,但它几乎不比推荐的按值传递技术好,在这种情况下,移动构造函数将用于这种情况。

我的想法是,析构函数将很快在"其他"上运行

那么你的想法是有缺陷的。你可以从任何你有非常量访问权限的对象中移动。在此之后,该对象可以无限期地继续存在。

从技术上讲,将当前数据放在旧对象中是正确的。但这不是一个好主意。最好使用堆栈变量:

Example& operator=(Example &&other)
{
    Example temp(std::move(other));  //other is now empty.
    std::swap(hExample, temp);       //our stuff is in `temp`, and will be destroyed
    return *thisl
}

或者更好的是(如果你没有使用Visual Studio)将你的东西存储在一个正确支持移动的包装器中,并让编译器生成的移动构造函数为你完成这项工作。