当前位置: 首页 > 知识库问答 >
问题:

为什么在声明移动操作时会删除复制操作?

缑兴贤
2023-03-14

当类显式声明复制操作(即复制构造函数或复制赋值操作符)时,不会为该类声明移动操作。但是当类显式声明移动操作时,复制操作被声明为删除。为什么会存在这种不对称?为什么不指定如果声明了移动操作,则不会声明复制操作?据我所知,不会有任何行为差异,也不需要对移动和复制操作进行不对称处理。

[对于喜欢引用该标准的人,12.8/9和12.8/20中规定了具有复制操作声明的类的移动操作声明的缺失,12.8/7和12.8/18中规定了具有移动操作声明的类的删除的复制操作。]

共有3个答案

赏梓
2023-03-14

它本质上是为了避免迁移代码来执行意想不到的不同动作。

复制和移动需要一定程度的一致性,所以C11——如果你只声明一个——抑制另一个。

考虑一下:

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

现在,如果我稍后添加一个move-ctor,这意味着我正在改变C的行为(在c03中定义C时,您没有计划到这一点),并且由于可移动对象不需要可复制(反之亦然),编译器假定通过使其可移动,dafault副本可能不再足够。这取决于我如何与移动保持一致,或者——如果我觉得足够的话——恢复C(const C

夏俊人
2023-03-14

为什么会存在这种不对称?

向后兼容,因为复制和移动之间的关系已经是不对称的。MoveConstructible的定义是CopyConstructible的一个特例,这意味着所有CopyConstructible类型也都是MoveConstructible类型。这是正确的,因为引用const的复制构造函数将处理右值和左值。

可复制类型可以在不使用移动构造函数的情况下从右值初始化(它可能没有使用移动构造函数那么有效)。

在移动基子对象时,复制构造函数还可用于在派生类的隐式定义的移动构造函数中执行“移动”。

因此,复制构造函数可以被视为“退化的移动构造函数”,因此,如果一个类型有一个复制构造函数,那么它并不严格需要移动构造函数,它已经是可移动构造函数了,因此不声明移动构造函数是可以接受的。

反之亦然,可移动类型不一定是可复制的,例如仅移动类型。在这些情况下,删除复制构造函数和赋值比不声明它们和获取关于将左值绑定到右值引用的错误提供了更好的诊断。

为什么不指定如果声明了移动操作,则不会声明复制操作?

更好的诊断和更明确的语义学。“定义为删除”是C11明确表示“不允许此操作”的方式,而不是碰巧被错误忽略或因其他原因丢失。

对于move构造函数和move赋值操作符,“not declared”的特例是不寻常的,并且由于上述不对称性而很特殊,但是特例通常最好保留在一些狭窄的情况下(这里值得注意的是,“not declared”也可以应用于默认构造函数)。

同样值得注意的是,你提到的一段[class.copy]p7说(强调我的):

如果类定义没有显式声明副本构造函数,则隐式声明副本构造函数。如果类定义声明了移动构造函数或移动赋值运算符,则隐式声明的复制构造函数定义为已删除;否则,它被定义为默认值(8.4)。如果类具有用户声明的复制赋值运算符或用户声明的析构函数,则不推荐使用后一种情况。

“后一种情况”是指“否则,它被定义为默认”部分。第18段对副本转让经营人有类似的措词。

因此,委员会的意图是,在C的未来版本中,其他类型的特殊成员函数也会导致复制构造函数和复制赋值操作符被删除。原因是,如果类需要用户定义的析构函数,那么隐式定义的复制行为可能不会做正确的事情。出于向后兼容性的原因,没有对C 11或C 14进行这种更改,但是在将来的版本中,为了防止复制构造函数和复制赋值运算符被删除,您需要显式声明它们,并将它们定义为默认值。

因此,如果复制构造函数可能没有做正确的事情,删除它们是一般情况,而“未声明”是移动构造函数的特殊情况,因为复制构造函数无论如何都可以提供退化的移动。

毋宪
2023-03-14

如果类要移动,但没有声明移动构造函数,则编译器会返回到复制构造函数。在相同的情况下,如果move构造函数被声明为deleted,那么程序将是格式错误的。因此,如果move构造函数被隐式声明为deleted,那么许多涉及现有pre-c11类的合理代码将无法编译。比如myVector。向后推(MyClass())

这解释了为什么在定义复制构造函数时不能隐式声明删除移动构造函数。这就留下了一个问题,为什么在定义移动构造函数时隐式声明删除复制构造函数。

我不知道委员会的确切动机,但我有一个猜测。如果向现有的C 03样式类添加移动构造函数将删除(以前隐式定义的)复制构造函数,那么使用该类的现有代码可能会以微妙的方式更改其含义,因为重载解析会选择意外的重载,而这些重载过去会被拒绝为更糟糕的匹配。

考虑:

struct C {
  C(int) {}
  operator int() { return 42; }
};

C a(1);
C b(a);  // (1)

这是一个遗留的C 03类。(1) 调用(隐式定义的)副本构造函数<代码>CB((int)a)也是可行的,但匹配性较差。

想象一下,不管出于什么原因,我决定向这个类添加一个显式的移动构造函数。如果移动构造函数的存在是为了抑制复制构造函数的隐式声明,那么(1)处一段看似不相关的代码仍然会编译,但会默默地改变它的含义:它现在会调用运算符int()C(int)。那会很糟糕。

另一方面,如果复制构造函数被隐式声明为已删除,那么(1)将无法编译,从而提醒我这个问题。我会检查情况并决定是否仍然需要一个默认的副本构造函数;如果是这样,我将添加C(const C

 类似资料:
  • 本文向大家介绍Prolog操作员声明,包括了Prolog操作员声明的使用技巧和注意事项,需要的朋友参考一下 示例 在Prolog中,可以使用op/3以下命令定义自定义运算符: op(+Precedence, +Type, :Operator) 声明运算符为具有优先级的类型的运算符。运算符也可以是名称列表,在这种情况下,列表的所有元素都声明为相同的运算符。 优先级是介于0和1200之间的整数,其中0

  • 问题内容: 是否有一个标准的Java库可以处理常见的文件操作,例如移动/复制文件/文件夹? 问题答案: 这是通过操作执行此操作的方法:

  • 主要内容:验证删除结果其他开发人员在更新他的本地存储库后,在目录中找到一个文件。查看提交消息后,了解到文件是由添加的。 现在,假设要对上面的项目中代码结构进行重构,代码文件:module.py 已经不再使用了,要将它删除,那么应该怎么做?请参考以下命令 - 验证删除结果 在另外一台电脑上,执行以下命令更新当前工作区,查看 目录中的文件是否还存在?

  • 顾名思义,移动操作将目录或文件从一个位置移动到另一个位置。例如,我们想要将源代码移动到src目录中。修改后的目录结构将显示如下: 为了使这些更改永久性,必须将修改的目录结构推送到远程存储库,以便其他开发人员可以看到这些更改。 在其它开发人员的本地存储库中,在执行操作之前,它将显示旧的目录结构。在另外一台开发者机器上,执行以下命令 - 但是在执行操作之后,目录结构将被更新。 现在,假设在另外一个开发

  • 命令用于从数据库和不同模式中删除表。参考以下步骤: 假设要从模式 db2inst1 中删除表。 可以看到该表在列表中可用,然后点击Execute SQL ,如下所示: 可以看到,表已经被删除了。如下图所示:

  • 如果要从数据库中删除某些记录,则需要DELETE操作。 以下程序显示了从AGE超过20的EMPLOYEE中删除所有记录的过程。 例子 (Example) /* Main program */ Call RxFuncAdd 'SQLLoadFuncs', 'rexxsql', 'SQLLoadFuncs' Call SQLLoadFuncs if SQLConnect(c1,'testuser