即使不使用移动构造函数,也需要它.为什么

Move constructor is required even if it is not used. Why?

本文关键字:为什么 构造函数 移动      更新时间:2023-10-16

为什么?!为什么C++要求类即使不使用也是可移动的!例如:

#include <iostream>
using namespace std;
struct A {
    const int idx;
    //   It could not be compileld if I comment out the next line and uncomment
    // the line after the next but the moving constructor is NOT called anyway!
    A(A&& a) : idx(a.idx) { cout<<"Moving constructor with idx="<<idx<<endl; }
   //  A(A&& a) = delete;
    A(const int i) : idx(i) { cout<<"Constructor with idx="<<i<<endl; }
    ~A() { cout<<"Destructor with idx="<<idx<<endl; }
};
int main()
{
    A a[2] = { 0, 1 };
   return 0;
}

输出是(不调用移动构造函数!

idx=0
的构造函数 idx=1
的构造函数 idx=1
的析构函数 idx=0 的析构函数

如果删除移动构造函数(">使用已删除的函数"A::A(A&&("(,则无法编译代码。但是,如果未删除构造函数,则不会使用它!多么愚蠢的限制?注意:我为什么需要它?当我尝试初始化包含unique_ptr字段的对象数组时,实际含义就出现了。例如:

// The array of this class can not be initialized!
class B {
    unique_ptr<int> ref;
public:
    B(int* ptr) : ref(ptr)
        {  }
}
// The next class can not be even compiled!
class C {
    B arrayOfB[2] = { NULL, NULL };
}

如果您尝试使用unique_ptr的向量,情况会变得更糟。

好。非常感谢大家。所有这些复制/移动构造函数和数组初始化都有很大的混淆。实际上,问题是关于编译器需要复制结构的情况,可以使用移动结构并且不使用它们。因此,当我获得普通键盘时,我将稍后创建一个新问题。我将在此处提供链接。

附言我创建了更具体和清晰的问题 - 欢迎讨论它!

A a[2] = { 0, 1 };

从概念上讲,这会创建两个临时A对象,A(0)A(1),并移动或复制它们以初始化数组a;因此需要一个移动或复制构造函数。

作为优化,允许省略移动或复制,这就是程序似乎不使用移动构造函数的原因。但是仍然必须有一个合适的构造函数,即使它被省略了。

A a[2] = { 0, 1 };

这是聚合初始化。 §8.5.1 [dcl.init.aggr]/p2 规定:

当聚合由初始值设定

项列表初始化时(如 8.5.4 中指定(,初始值设定项列表的元素将作为聚合成员的初始值设定项,按下标或成员顺序递增。每个成员都从相应的初始值设定项子句进行复制初始化

§8.5 [dcl.init]/p16 反过来描述了类类型对象的副本初始化的语义:

  • [...]
  • 如果目标类型是(可能符合 cv 条件的(类类型:
    • 如果初始化是直接初始化,或者如果是复制初始化,其中源的 cv 非限定版本 类型与 目标,考虑构造函数。适用的构造函数 枚举 (13.3.1.3(,并通过重载选择最佳 分辨率 (13.3(。调用如此选择的构造函数来初始化 对象,其中初始值设定项表达式或表达式列表为其 参数。如果没有构造函数应用,或者重载解析为 模棱两可,初始化格式不正确。
    • 否则(即,对于剩余的复制初始化情况(,可以从源转换的用户定义的转换序列 键入为目标类型或(使用转换函数时( 如13.3.1.4中所述枚举其派生类, 最好的一个是通过过载分辨率(13.3(选择的。如果 转换无法完成或模棱两可,初始化为 格式不正确。所选函数使用初始值设定项调用 表达作为其参数;如果函数是构造函数,则调用 初始化 CV 非限定版本的临时版本 目标类型。临时是原则。调用的结果 (这是构造函数情况的临时(然后用于 根据上述规则,直接初始化对象 复制初始化的目标。在某些情况下,一个 允许实施以消除此固有的复制 通过直接构造中间结果进行直接初始化 到正在初始化的对象中;参见 12.2、12.8。

由于 01int s,而不是 A s,因此此处的副本初始化属于第二个子句。编译器在A::A(int)构造函数中找到从intA的用户定义转换,调用它来构造类型A的临时,然后使用该临时函数执行直接初始化。反过来,这意味着编译器需要为采用 A 类型的临时构造函数执行重载解析,这会选择已删除的移动构造函数,从而使程序格式不正确。