使用move-constructor时将self重置为nullptr是一个好习惯吗?

时间:2017-04-13 08:44:32

标签: c++ c++11

在C ++ 11中,move-constructor / operator支持资源/内存移动。

这是我的例子:

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_;
};

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

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.
    }
};

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

我在何时应该使用a_'s table_将tmp分配给a_,而不是复制。

在答案的帮助下,我修改了A的移动构造函数,如下所示:

std::move

在这段代码中,当我移动某些东西时,我会交换旧的和旧的引用,因此旧的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_); } }; 将删除[]原始的a_ table_,这是无用的。

这是一个很好的习惯。

6 个答案:

答案 0 :(得分:4)

当您从other中的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;
}

这会将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;
}

这样,当从对象移动时,将完成释放。

答案 1 :(得分:2)

这段代码有很多问题(即使你想要摆弄数组和指针,你现在也不应该这样做。只需使用std :: vector)。

错误代码:

<li  *ngIf="Games?.length == 0">
    <span class="search_no_results">
       No data found 
    </span>
</li>

请勿在ctor正文中使用赋值,请使用member-init-lists。好代码:

A()
{
    table_ = nullptr;
    alloc_ = 0;
}

同样适用于其他构造者。

冗余代码:

A() : table{nullptr}, alloc_ {0} {}

if (table_) delete[] table_; 将再次检查您的指针。 delete delete非常安全。不要打扰。

非常错误代码:

nullptr

A(const A & other) { if (table_) delete[] table_; 未初始化。访问它是UB。而且,没有必要在构造函数中进行此检查。新构造的对象中不会有任何分配。只需删除支票即可。与其他建设者一样。

错误代码:

table

不防止自我指派。同样适用于其他任务操作员。

无论您是编写C ++ 03还是C ++ 11代码,都需要学习这些习惯。现在移动:

A& operator=(const A & other)
{
    if (table_)
        delete[] table_;

这是完全错误的。您需要更改移动的对象,否则它根本不是移动,只是简单的浅层复制。

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

同样适合移动工作。

移动中的

A(A && other) : table_{other.table_}, alloc_{other.alloc_} { { other.table_ = nullptr; other.alloc_ = 0; } 当你处理用户定义的类型时,ctor是一个很好的习惯用法。原始类型并不完全需要,主要是因为你需要先将它们初始化然后立即交换,但你可以使用它。

答案 2 :(得分:1)

您的移动构造函数和赋值运算符正在有效地执行浅拷贝。您应该将other.table设置为nullptr,以便在这种情况下有意义。当然,这将避免两次删除相同数组时的未定义行为,如您在示例中所建议的那样。

答案 3 :(得分:1)

一个很好的选择是交换移动构造函数中的值。

A& operator=(A && other)
{
    using namespace std;
    swap(table_, other.table_);
    swap(alloc_, other.alloc_);
    return *this;
}

这样,源的内容被放置在目标中,后者的内容被转移到源 - 然后在被删除时将正确地清理它们(这是你期望的,否则,你不会想要移动对象......)。

移动构造函数可以从上面的赋值中获利:

A(A&& other) : A()
{
    *this = std::move(other);
}

答案 4 :(得分:1)

在移动构造函数/赋值中,复制指针后,将它们指定为“nullptr”,这样当析构函数被调用时,它将不是操作。

这是我如何去编写移动构造函数和赋值。而且,你可以避免“如果”检查“删除”,如果它是“nullptr”,它将不是操作。

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

    A& operator=(A && other) {
        delete[] table_;
        table_ = other.table_;
        other.table_ = nullptr; // assign the source to be nullptr
        alloc_ = other.alloc_;
        return *this;
    }

答案 5 :(得分:0)

我认为您需要更改移动构造函数赋值运算符,如下所示:

// Simple move constructor
A(A&& arg) : member(std::move(arg.member)) // the expression "arg.member" is lvalue
{} 
// Simple move assignment operator
A& operator=(A&& other) {
     member = std::move(other.member);
     return *this;
}

定义为here