隐式移动构造函数和赋值运算符

Posted

技术标签:

【中文标题】隐式移动构造函数和赋值运算符【英文标题】:Implicit move constructor and assignment operator 【发布时间】:2020-09-06 21:04:06 【问题描述】:

隐式移动构造函数执行成员方式移动和隐式移动赋值运算符执行成员方式分配是什么意思?

来自https://en.cppreference.com/w/cpp/language/move_constructor:

对于非联合类类型(类和结构),move 构造函数 对对象的基础和非静态执行完整的成员移动 成员,按其初始化顺序,使用直接初始化 带有 xvalue 参数。如果这满足一个要求 constexpr构造函数,生成的move构造函数是constexpr。

来自https://en.cppreference.com/w/cpp/language/move_assignment:

对于非联合类类型(class 和 struct),move 赋值 运算符执行对象的完整成员移动分配 直接基数和直接非静态成员,在其声明中 顺序,对标量使用内置赋值,按成员 数组的移动赋值,类的移动赋值运算符 类型(非虚拟调用)。

以下示例类模板的隐式成员是否如下所示:

template<class T>
class Holder 
public:
    Holder(int size) : m_size(size)  m_data = new T[m_size]; 

    Holder(Holder && other) :
        m_size(std::move(other.m_size)),
        m_data(std::move(other.m_data))
    

    Holder& operator=(Holder && other) 
       if(this == &other) return *this;
       m_data = std::move(other.m_data);
       m_size = std::move(other.m_size);
       return *this;
    

    ~Holder()  delete [] m_data; 
private:
    T* m_data;
    int m_size;
;

还有,上例中的std::move()会转移什么资源?

【问题讨论】:

如果您要实现一个移动构造函数,该构造函数单独移动每个基数以及每个成员 - 并在初始化列表中执行此操作 - 您将完全实现“完整成员明智的举动”。同样,如果您实现移动赋值运算符来移动所有基和所有成员。 移动原始指针(如您的m_data)只会导致指针的副本,这通常会破坏程序。 m_data(std::exchange(other.m_data, nullptr)) 在您的情况下可能是正确的做法。 是的,但我的意思是隐式析构函数和复制运算符会做什么? Holder被销毁时,T*int占用的空间将被释放。如果没有显式析构函数,m_data 指向的任何数据都将被泄露。它会不会 delete[] 它为您服务。隐式复制运算符只会复制值。它不会复制m_data 指向的内容。如果你使用它,你将有两个指向相同数据的对象,并且它会被释放两次(UB)。 【参考方案1】:

如果您进一步向下查看链接页面,您会看到您的类编译器生成的移动构造函数(和移动赋值运算符)实际上是琐碎的

简单的移动构造函数

如果满足以下所有条件,则类 T 的移动构造函数是微不足道的:

它不是用户提供的(意味着它是隐式定义或默认的); T 没有虚成员函数; T 没有虚拟基类 为 T 的每个直接基数选择的移动构造函数是微不足道的; 为 T 的每个非静态类类型(或类类型的数组)成员选择的移动构造函数是微不足道的;

普通移动构造函数是执行与普通复制构造函数相同的操作的构造函数,即复制 对象表示就像 std::memmove。所有数据类型 与 C 语言兼容(POD 类型)可轻松移动。

(强调我的)

这两个成员变量是 POD 类型,因此可以轻松移动。由于您的类不是虚拟的并且它不包含非平凡的成员,因此它是平凡的并且所有数据成员都将被复制。如 cmets 中所述,这将导致双重删除您的指针和 UB。

既然是这种情况,您需要正确实现移动语义,方法是获取移动对象指针的所有权并将其设置为nullptr。或者更好的是,只需使用std::vector 甚至std::unique_ptr

【讨论】:

好的,谢谢。一个问题 - 这个隐式移动构造函数的定义方式是否与我在问题中提出的方式相同,但仅使用 std::move() 所有成员将获取传递的临时对象的相应成员的副本? 不,行为将是相似的,但按照说明,您不要使用 std::move 。从引用:制作对象表示的副本,就好像通过 std::memmove 好的,那么如果我添加一些虚拟成员函数会发生什么变化? 可能值得另一个问题,但大致相同。您的类移动构造函数将不再是微不足道的,但成员变量将是微不足道的,因此指针仍然只是被复制,您仍然遇到同样的问题。 但是移动构造函数会是什么样子呢?那么会和问题中的一样吗?

以上是关于隐式移动构造函数和赋值运算符的主要内容,如果未能解决你的问题,请参考以下文章

拷贝控制——拷贝赋值与销毁

对象的赋值运算符

C++C++的拷贝控制

派生类在基类中删除时是不是会有隐式复制构造函数或赋值运算符?

C++ 拷贝构造函数和赋值运算符

c++ 拷贝构造函数与赋值运算符重载函数的区别是