使用移动构造函数时将 self 重置为 nullptr 是一个好习惯吗?

lin*_*bin 5 c++ c++11

在 C++11 中,移动构造函数/运算符支持资源/内存移动。

这是我的例子:

class A {
public:
    A() : table_(nullptr), alloc_(0) {}
    ~A()
    {
        if (table_)
            delete[] table_;
    }

    A(const A & other)
    {
        // table_ is not initialized
        // if (table_)
        //    delete[] table_;
        table_ = new int[other.alloc_];
        memcpy(table_, other.table_, other.alloc_ * sizeof(int));
        alloc_ = other.alloc_;
    }
    A& operator=(const A & other)
    {
        if (table_)
            delete[] table_;
        table_ = new int[other.alloc_];
        memcpy(table_, other.table_, other.alloc_ * sizeof(int));
        alloc_ = other.alloc_;
        return *this;
    }

    A(A && other)
    {
        // table_ is not initialized in constructor
        // if (table_)
        //    delete[] table_;
        table_ = other.table_;
        alloc_ = other.alloc_;
    }

    A& operator=(A && other)
    {
        if (table_)
            delete[] table_;
        table_ = other.table_;
        alloc_ = other.alloc_;
    }

private:
    int *table_;
    int alloc_;
};
Run Code Online (Sandbox Code Playgroud)

看起来不错,但有时我想移动一个局部变量,如下所示:

class B {
private:
    A a_;

public:
    void hello()
    {
        A tmp;
        // do something to tmp
        a_ = std::move(tmp);
        // tmp.~A() is called, so a_ is invalid now.
    }
};
Run Code Online (Sandbox Code Playgroud)

当函数结束时,tmp.~A()将被调用,此时,a_tmp具有相同的table_指针,当tmp delete[] table_a_'s table_将无效。

我在徘徊什么时候应该使用std::move将 tmp 分配给 a_,而无需复制。

在答案的帮助下,我像这样修改了 A 的移动构造函数:

class A {
private:
    void reset()
    {
        table_ = nullptr;
        alloc_ = 0;
    }

public:

    A(A && other)
    {
        table_ = other.table_;
        alloc_ = other.alloc_;
        other.reset();
    }

    A& operator=(A && other)
    {
        std::swap(table_, other.table_);
        std::swap(alloc_, other.alloc_);
    }
};
Run Code Online (Sandbox Code Playgroud)

在这段代码中,当我移动一些东西时,我会交换新旧引用,因此旧的tmp将删除[]原始a_ table_,这是无用的。

这是一个很好的习惯。

mar*_*inj 5

当您从otherin移动时A(A && other),您还应该将其移动的数据成员设置为 nulltpr。所以固定代码应该如下所示:

A(A && other)
{
    //if (table_)
    //    delete[] table_; // no need for this in move c-tor
    table_ = other.table_;
    other.table_ = nullptr;
    alloc_ = other.alloc_;
    other.alloc_ = nullptr;
}

A& operator=(A && other)
{
    // as n.m. has pointed out, this move assignment does not 
    // protect against self assignment. One solution is to use
    // swap aproach here. The other is to simply check if table_ == other.table_. 
    // Also see here for drawbacks of swap method:
    // http://scottmeyers.blogspot.com/2014/06/the-drawbacks-of-implementing-move.html
    delete[] table_;
    table_ = other.table_;
    other.table_ = nullptr;
    alloc_ = other.alloc_;
    other.alloc_ = nullptr;
    return *this;
}
Run Code Online (Sandbox Code Playgroud)

这放入other了标准调用的内容valid but unspecified state

你也可以使用 std::swap 如下:

A(A && other)
{
    table_ = other.table_;
    alloc_ = other.alloc_;
}

A& operator=(A && other)
{
    std::swap(table_, other.table_);
    std::swap(alloc_, other.alloc_);
    return *this;
}
Run Code Online (Sandbox Code Playgroud)

当从对象被销毁时,这种方式将完成释放。