根据移动构造函数实现复制赋值运算符

时间:2016-09-08 08:34:17

标签: c++ c++11 move-semantics

考虑以下概念/示例类

class Test
{
public:
    explicit Test(std::string arg_string)
        : my_string( std::move(arg_string) )
    { }

    Test(const Test& Copy) {
        this->my_string = Copy.my_string;
    }

    Test& operator=(Test Copy) {
        MoveImpl( std::move(Copy) );
        return *this;
    }

    Test(Test&& Moved) {
        MoveImpl( std::forward<Test&&>(Moved) );
    }

    Test& operator=(Test&& Moved) {
        MoveImpl( std::forward<Test&&>(Moved) );
        return *this;
    }

private:
    void MoveImpl(Test&& MoveObj) {
        this->my_string = std::move(MoveObj.my_string);
    }

    std::string my_string;
};

复制构造函数照常使用const&

复制赋值运算符是根据复制构造函数实现的(如果我没记错的话,Scott Meyers指出异常安全和自我赋值问题以这种方式解决)。

当实现移动构造函数和移动赋值运算符时,我认为存在一些“代码重复”,我通过添加MoveImpl(&&)私有方法来“消除”。

我的问题是,因为我们知道复制赋值运算符获取了一个新的对象副本,该副本将在作用域结束时被清除,使用MoveImpl()函数实现是否正确/良好实践复制赋值运算符的功能。

2 个答案:

答案 0 :(得分:3)

复制赋值运算符的按值签名的优点在于它不需要移动赋值运算符(前提是您正确定义了移动构造函数!)。

class Test
{
public:
    explicit Test(std::string arg_string)
        : my_string( std::move(arg_string) )
    { }

    Test(const Test& Copy)
        : my_string(Copy.my_string)
    { }

    Test(Test&& Moved)
        : my_string( std::move(Moved.my_string) )
    { }

    // other will be initialized using the move constructor if the actual
    // argument in the assignment statement is an rvalue
    Test& operator=(Test other)
    {
        swap(other);
        return *this;
    }

    void swap(Test& other)
    {
        std::swap(my_string, other.my_string);
    }

private:
    std::string my_string;
};

答案 1 :(得分:1)

你的想法是正确的,但共同点在于交换操作。

如果你之前尝试这样做,你将失去在构造函数初始化列表中初始化成员的机会,这在概念上会导致成员的冗余默认初始化以及整齐地处理异常的困难。

这更接近您所追求的模型:

class Test
{
public:
    explicit Test(std::string arg_string)
    : my_string( std::move(arg_string) )
    { }

    Test(const Test& Copy) : my_string(Copy.my_string)
    {
    }

    Test& operator=(Test const& Copy)
    {
        auto tmp(Copy);
        swap(tmp);
        return *this;
    }

    Test(Test&& Moved) : my_string(std::move(Moved.my_string))
    {
    }

    Test& operator=(Test&& Moved)
    {
        auto tmp = std::move(Moved);
        swap(tmp);
        return *this;
    }

    void swap(Test& other) noexcept
    {
        using std::swap;
        swap(my_string, other.my_string);
    }

private:

    std::string my_string;
};

当然,实际上,除非你绝对需要在析构函数中进行特殊处理(你几乎从不这样做),否则零规则应该总是优先考虑:

class Test
{
public:
    explicit Test(std::string arg_string)
    : my_string( std::move(arg_string) )
    { }

// copy, assignment, move and move-assign are auto-generated
// as is destructor

private:

    std::string my_string;
};