使用非指针数据成员移动语义

Move semantics with non-pointer data members

可能已经有人询问并回答了这个问题,但我不知道要搜索什么。

如果数据成员定义了移动赋值运算符,是否可以将移动语义用于非指针数据成员?

假设我有一个 class M 定义 M::operator=(M&&) 如下:

template <class T>
class M
{
public: 
    M()
    {
        mem_M = new T;
    }

    M& operator=(M&& src)
    {
        if (this != &src)
        {
            mem_M = src.mem_M;
            src.mem_M = nullptr;
        }
        return *this;
    }

private:    
    T* mem_M;
};

现在显然我可以有一个像这样的 class C<T>,带有一个不使用 T 的移动赋值运算符的移动构造函数:

template <class T>
class C
{
public:
    C ()
    {
        mem_C = new T;
    }
    C (C&& rhs)
    {
        mem_C = rhs.mem_C;
        rhs.mem_C = nullptr;
    }

private:
    T* mem_C;
};

但是,如果我希望 C<T>::mem_C 不是指针而是普通成员,我将如何处理移动函数中的 C<T>::mem_C 呢?我当然可以调用移动赋值运算符 T::operator=(T&&) 将提交的 mem_C 从一个实例移动到另一个实例,但是如何正确重置传递给 [=22= 的 C 实例]?

这至少在我看来是错误的:

template <class T>
class C
{
public:
    C ()
    {
        mem_C = T();
    }
    C (C<T>&& rhs)
    {
        mem_C = std::move(rhs.mem_C);
        rhs.mem_C = T();          // ?? like this?
    }

private:
    T mem_C;
};

那么,在移动函数中重置非指针数据成员的符合标准的方法是什么?

包含类型的 assignment/constructors 移动必须使对象处于 "acceptable" 状态,无论该类型对该类型意味着什么。正在移动的类型之外的任何内容都不应负责维护对象的状态。

此外,您要确保在父移动 构造函数 中调用包含类型的移动 构造函数,而不是包含的类型的移动 assignment 正如你在你的例子中:

// move constructor calls move constructor of contained elements
C (C<T>&& rhs) : mem_c(std::move(rhs.mem_c))
{
    // anything in here is using already-constructed data members
}

// move assignment calls move assignment of contained elements
C & operator=(C<T>&& rhs) {
    mem_c = std::move(rhs.mem_c);
}