C++ 为什么在声明移动操作时会删除复制操作?
当类显式声明复制操作(即复制构造函数或复制赋值运算符)时,不会为该类声明移动操作。但当类显式声明移动操作时,复制操作将声明为已删除。为什么会存在这种不对称?为什么不指定如果声明了移动操作,则不会声明复制操作?据我所知,不会有任何行为差异,也不需要对移动和复制操作进行不对称处理C++ 为什么在声明移动操作时会删除复制操作?,c++,c++11,copy-constructor,move-semantics,C++,C++11,Copy Constructor,Move Semantics,当类显式声明复制操作(即复制构造函数或复制赋值运算符)时,不会为该类声明移动操作。但当类显式声明移动操作时,复制操作将声明为已删除。为什么会存在这种不对称?为什么不指定如果声明了移动操作,则不会声明复制操作?据我所知,不会有任何行为差异,也不需要对移动和复制操作进行不对称处理 [对于喜欢引用本标准的人,在12.8/9和12.8/20中规定了具有复制操作声明的类没有声明移动操作,在12.8/7和12.8/18中规定了具有移动操作声明的类的删除复制操作。]如果类要移动,但由于没有声明移动构造函数,编
[对于喜欢引用本标准的人,在12.8/9和12.8/20中规定了具有复制操作声明的类没有声明移动操作,在12.8/7和12.8/18中规定了具有移动操作声明的类的删除复制操作。]如果类要移动,但由于没有声明移动构造函数,编译器会返回到复制构造函数。在相同的情况下,如果move构造函数被声明为deleted,那么程序将是格式错误的。因此,如果move构造函数被隐式声明为deleted,那么许多涉及现有C++11之前的类的合理代码将无法编译。类似于
myVector.push_back(MyClass())
这解释了为什么在定义复制构造函数时不能隐式声明删除移动构造函数。这就留下了一个问题,即在定义移动构造函数时,为什么隐式声明删除复制构造函数
我不知道委员会的确切动机,但我有一个猜测。如果向现有的C++03样式类添加移动构造函数将删除(以前隐式定义的)复制构造函数,那么使用该类的现有代码可能会以微妙的方式更改其含义,因为重载解析会选择意外的重载,而这些重载过去会被拒绝为更糟糕的匹配
考虑:
struct C {
C(int) {}
operator int() { return 42; }
};
C a(1);
C b(a); // (1)
这是一个遗留的C++03类。(1) 调用(隐式定义的)副本构造函数<代码>CB((int)a)也是可行的,但匹配性较差
想象一下,无论出于什么原因,我决定向这个类添加一个显式的move构造函数。如果move构造函数的存在是为了抑制copy构造函数的隐式声明,那么(1)处看似无关的代码段仍将编译,但会悄悄地改变其含义:它现在将调用操作符int()
和C(int)
。那太糟糕了
另一方面,如果复制构造函数被隐式声明为已删除,那么(1)将无法编译,从而提醒我这个问题。我会检查情况并决定是否仍然需要一个默认的副本构造函数;如果是这样,我将添加
C(const C&)=default代码>基本上是为了避免迁移的代码执行意外的不同操作
复制和移动需要一定程度的一致性,因此C++11(如果只声明一个)会抑制另一个
考虑这一点:
C a(1); //init
C b(a); //copy
C c(C(1)); //copy from temporary (03) or move(11).
假设您用C++03编写这个
假设我稍后在C++11中编译它。
如果未声明任何ctor,则默认移动将进行复制(因此最终行为与C++03相同)
如果声明了copy,move将被删除,sineC&&
将衰减为C const&
第三条语句将从临时语句生成副本。这仍然是一个C++03相同的行为
现在,如果我稍后添加一个move-ctor,这意味着我正在改变C的行为(在C++03中定义C时,您没有计划到这一点),并且由于可移动对象不需要可复制(反之亦然),编译器假定通过使其可移动,dafault副本可能不再足够。这取决于我如何与移动一致地实现它,或者——如果我觉得它足够的话——恢复C(const C&)=default代码>
为什么会存在这种不对称
向后兼容,因为复制和移动之间的关系已经是不对称的。MoveConstructible的定义是CopyConstructible的一个特例,这意味着所有CopyConstructible类型也都是MoveConstructible类型。这是正确的,因为引用const的复制构造函数将处理右值和左值
可复制类型可以在不使用移动构造函数的情况下从右值初始化(它可能没有使用移动构造函数那么有效)
在移动基子对象时,复制构造函数还可用于在派生类的隐式定义的移动构造函数中执行“移动”
因此,复制构造函数可以被视为“退化的移动构造函数”,因此,如果一个类型有一个复制构造函数,那么它并不严格需要移动构造函数,它已经是可移动构造函数了,因此不声明移动构造函数是可以接受的
反之亦然,可移动类型不一定是可复制的,例如仅移动类型。在这些情况下,删除复制构造函数和赋值比不声明它们和获取关于将左值绑定到右值引用的错误提供了更好的诊断
为什么不指定如果声明了移动操作,则不会声明复制操作
更好的诊断和更明确的语义。“definedasdeleted”是C++11明确表示“此操作是不允许的”的方式,而不是碰巧由于错误或其他原因而遗漏
对于move构造函数和move赋值操作符,“not declared”的特例是不寻常的,并且由于上述不对称性而很特殊,但是特例通常最好保留在一些狭窄的情况下(这里值得注意的是,“not declared”也可以应用于默认构造函数)
另外值得注意的是,您提到的[class.copy]第7页的一段中说(我的重点):
如果类定义没有显式声明副本构造函数,则隐式声明副本构造函数。如果类定义声明移动