【问题标题】:What are the rules for automatic generation of move operations?自动生成移动操作的规则是什么?
【发布时间】:2014-08-12 03:16:47
【问题描述】:

在 C++98 中,C++ 编译器可以自动通过成员复制生成复制构造函数和复制赋值运算符,例如

struct X {
    std::string s;
    std::vector<int> v;
    int n;
};

编译器自动X生成复制构造函数和复制赋值运算符,使用成员复制。

但是 C++11 中的移动语义如何改变?

move constructormove 赋值运算符 自动生成,比如复制构造函数和复制赋值运算符?

是否存在移动操作自动生成的情况?

【问题讨论】:

  • 自动移动是一种优化,因此理想情况下不应影响除速度之外的任何可观察行为。但是例如用户定义的析构函数可能依赖于关于数据成员的各种假设。 scott meyers 曾经在 clc++ 和随后的 comp.std.c++ 中开始对此进行长时间的讨论,而当前的规则(我很不清楚)反映了该讨论。
  • 如果您喜欢图形、表格结果,请参阅github.com/HowardHinnant/presentations/blob/master/accu_2014/… 的第 28 张幻灯片(有报道称,某些 pdf 阅读器显示效果不佳,如果是这种情况,请尝试另一个)。
  • @HowardHinnant:您的幻灯片看起来很棒。我将有一个更详细的外观。感谢分享。
  • @HowardHinnant:如果您想写一个链接幻灯片的答案,我很乐意支持。
  • Visual Studio 2010-2013 不会生成它们,期间。来自 MSDN 上的Support For C++11/14/17 Features“Rvalue 引用 v3.0 添加了新规则以在特定条件下自动生成移动构造函数和移动赋值运算符。这是在 Visual Studio 2015 中实现的”。他们所说的“Rvalue references v3.0”N3053, Defining Move Special Member Functions (MARCH 2010)

标签: c++ c++11 move-semantics


【解决方案1】:

来自标准的 Ch. 12 - 特殊成员函数

Par 12.8 复制和移动类对象(强调我的)

9 .如果类 X 的定义没有显式声明移动构造函数,当且仅当

时,一个将被隐式声明为默认值

— X 没有用户声明的复制构造函数,

——X 没有用户声明的复制赋值运算符,

— X 没有用户声明的移动赋值运算符,并且

— X 没有用户声明的析构函数。

[ 注意:当移动构造函数没有被隐式声明或显式提供时,表达式 否则会调用移动构造函数,可能会调用复制构造函数。 ——尾注]

然后11解释删除默认移动构造函数的规则

11 .隐式声明的复制/移动构造函数是其类的内联公共成员。 如果 X 具有以下条件,则类 X 的默认复制/移动构造函数被定义为已删除 (8.4.3):

- 具有非平凡对应构造函数的变体成员,并且 X 是类联合类,

- 类类型 M(或其数组)的非静态数据成员,由于重载决议 (13.3) 应用于 M 的相应构造函数,导致歧义或函数被删除或删除,因此无法复制/移动无法从默认构造函数访问,

- 无法复制/移动的直接或虚拟基类 B,因为应用于 B 的相应构造函数的重载解析 (13.3) 会导致歧义或从默认构造函数中删除或无法访问的函数,

——任何直接或虚拟基类或具有析构函数的类型的非静态数据成员,该析构函数已从默认构造函数中删除或不可访问,或者,

——对于复制构造函数,右值引用类型的非静态数据成员。定义为已删除的默认移动构造函数会被重载决议(13.3、13.4)忽略。

[ 注意:删除的移动构造函数会干扰右值的初始化,而右值可以使用复制构造函数。 ——尾注]


关于这一切的复杂性*

规则可能有点压倒性。最好使用一些技术绕过复杂性。例子是:

  1. 利用rule of zero 来简化大部分课程的编写。
  2. (隐式删除)显式默认有问题的特殊成员函数;如果它被隐式定义为删除,编译器会报错。

* 我自己 (1) 和 dyp (2) 在 cmets 中取得的分数

【讨论】:

  • 这些似乎是 CWG-1402 之后的规则;尽管这些“更好”,因为许多编译器供应商会将它们用于他们的 C++11 模式,但这些规则并不是“来自标准”。
  • @Mr.C64 规则非常总是复杂。我喜欢rule of zero - 如果我不得不偏离它,我必须保持相关的标准页面打开(我仍然对隐式声明的特殊成员函数不安全)
  • 您可以显式默认它们;如果它们被隐式定义为删除,编译器会抱怨。
  • @dyp 喜欢诙谐的快捷方式。惊人的 !!我的意思是 C++ 不需要更多的规则,它需要这样的思维,以便知识可以维护,否则它会失控(恕我直言)
  • 请注意,您仍然需要考虑 隐式定义的版本做了什么。 IE。 没有被隐式声明被隐式定义为删除是有区别的。
【解决方案2】:

Nikos Athanasiou 给出了一个很好的答案,但我想添加这个我认为非常有用的工具。

这里是 Howard Hinnant 在 ACCU 2014 会议上的演讲 "Everything You Ever Wanted To Know About Move Semantics (and then some)" 的截图,我认为这很好地提醒了自动生成特殊成员的规则:

来自 cmets 的 Hinnant 先生的澄清:

幻灯片上没有说,但红色方块表示已弃用 行为。 IE。如果您不想依赖已弃用的行为, 如果你声明你的析构函数,那么声明你的两个副本成员, 或复制成员之一(基本上遵循C++98/03“规则3”)

我建议阅读the slides 以了解此表的渐进式构造,并详细说明我们现在如何以及为什么拥有此表。

可以在此处找到其他演示文稿:http://accu.org/index.php/articles/1901

【讨论】:

  • +1 和信息:幻灯片没有说明,但红色方块表示已弃用的行为。 IE。如果您不想依赖已弃用的行为,则在声明析构函数或复制成员之一时声明您的两个复制成员(基本上遵循 C++98/03“3 规则”)。跨度>
  • @HowardHinnant 感谢您的澄清,我在答案中添加了它。
  • @HowardHinnant 鉴于已弃用的功能(红色),一旦功能被删除,这些成员将变成什么?会“未声明”还是“删除”?
  • @Niall:我不知道。我什至没有一个好的猜测。我认为您提到的任何一个选项都是可能的。今天,最有可能的猜测是它们永远不会被删除。今天的编译器甚至不愿意默认打开这个已弃用的警告。我正在努力通过教育扭转这一趋势。我认为最终通过任何选择移除都是一个好的方向。
  • 这会是一张不错的海报
猜你喜欢
  • 2016-09-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-11-07
  • 1970-01-01
相关资源
最近更新 更多