【问题标题】:MSVC cannot return an object that can be copied but cannot be movedMSVC 无法返回可复制但无法移动的对象
【发布时间】:2021-05-29 22:22:20
【问题描述】:

在摆弄复制省略时,我遇到了这种奇怪的行为:

class Obj {
 public:
  Obj() = default;

  Obj(Obj&&) = delete;
  Obj(const Obj&) { std::cout << "Copy" << std::endl; }
};

Obj f1() {
  Obj o;
  return o; // error C2280: move constructor is deleted
}

Obj f2() {
  Obj o;
  return Obj(o); // this however works fine
}

int main() {
  Obj p = f1();
  Obj q = f2();

  return 0;
}

GCC 和 Clang 接受此代码,并且能够在这两种情况下使用复制省略。

f1() MSVC 抱怨它不能返回o,因为Obj 的移动构造函数被删除了。但是,我希望它能够依赖于复制构造函数。 这是 MSVC 中的错误还是这种期望的行为(我不明白)和 GCC / Clang 过于宽松?

如果我提供一个移动构造函数,MSVC 可以在编译为 Release 时省略移动。

有趣的是,MSVC 能够编译 f2()。据我了解,这是由于返回构造函数调用的结果时强制复制省略所致。但是,如果我手动复制它,我只能按值返回 o,这感觉违反直觉。

我知道这种情况可能与实际使用无关,因为可复制对象通常也是可移动的,但我对底层机制很感兴趣。

这里是在线测试示例:https://godbolt.org/z/sznds7

【问题讨论】:

  • 在 f1 中,复制省略在 f2 中是可选的。 Msvc 是对的,但 gcc 和 clang 也是。见en.cppreference.com/w/cpp/language/copy_elision
  • 虽然deleted 函数确实参与重载决议,但似乎有一个exception 用于特殊成员函数,它们被显式忽略。在我看来,这就像一个 MSVC 错误。
  • @cigien - 这适用吗?此移动构造函数不是 默认 移动 (= default) ...
  • @cigien - 但在该部分给出的示例中,正在讨论的移动构造函数,对于struct B,是= default,但它被“定义”删除,因为struct B包含一个成员是一个结构,它有一个已删除的 (= delete) 移动构造函数 ...
  • @cigien 当一个特殊的成员函数被声明为默认值时,编译器会提供一个遵循一组规则的隐式定义。有时,这些规则说应该删除该函数(例如,具有不可移动成员的类的移动构造函数);然后该功能被“定义为已删除”。在这种情况下,重载决议的行为就像根本没有声明函数一样。

标签: c++ visual-c++ c++17 language-lawyer copy-elision


【解决方案1】:

f1() 没有错误是 clang 和 gcc 中的一个错误。 It is fixed in clang's tip-of-trunk.

f1() 不符合强制复制省略的条件。

删除的函数参与重载决议。如果它们被选为最佳重载,则程序格式错误。在f1() 中,已删除的移动构造函数由重载决议选择。

f2()as of C++17, copy/move elision is guaranteed 中,未完成移动/复制构造函数的重载决议。在 C++11/14 中,f2() 也是一个错误(与f1() 相同的错误),因为复制/移动省略是保证的。

另请参阅此指南:Never delete the special move members,这是在 C++17 之前编写的。

【讨论】:

  • 我对此有些困惑,听起来您是在说 GCC/Clang 解析为已删除的移动构造函数。那是A)编译器中的错误,B)规范中定义的行为,还是C)未定义的行为(使其不是错误)?听起来你在说 C,所以 f1 对于具有已删除移动构造函数的对象总是会有未定义的行为。如果是这种情况,那么 GCC/Clang 是否定义了没有复制构造函数的移动构造函数?如果是 UB 是 bug 吗?
  • 答案的第一句话就是整个答案。其他一切都是第一句话的支持信息。
  • @jrh 格式错误的程序不是 UB。格式错误的程序需要诊断(错误)。格式错误的程序,不需要诊断,可以编译和生成 UB,但这不是格式错误的 NDR 案例;我上次数过,你一方面可以计算格式不正确的 NDR 案例。
  • 感谢@Yakk-AdamNevraumont。你理解 jrh 的问题,而我没有。好答案。在这一行工作了几十年后,我陷入了误用标准的陷阱——说简单的英语。
【解决方案2】:

哦,我感到很惭愧,我才意识到另一个答案是霍华德·欣南特的,他的著作让我明白了我在这里痛苦地试图解释的内容,这有点荒谬......强>

由于复制和移动构造函数都被声明,它们都存在。 尤其是在这里,您小心地定义了自己的复制构造函数;否则,它将被定义为删除(参见this presentation 的第 28 页)。

deleted 方面只是定义的详细信息,但实际上它们都被声明为有资格重载决议。 在f1()中如果发生了复制省略,则无需在复制和移动构造函数之间进行选择;这些都没有使用。 另一方面,如果没有发生复制省略,则必须选择最佳重载来构造结果;这里这是move构造函数,因为它存在(已声明,见here),最后发现定义为已删除,但为时已晚,选择已经做出。

f2() 中,显式请求显式复制,则复制构造函数是最佳选择。

令人困惑的是,当我们阅读=delete 时,我们认为“这不能在重载决议中选择”但这是错误的; =delete 仅在重载决议之后才被考虑,当找到更好的匹配时为时已晚。

【讨论】:

  • 不是很可笑,而是很有趣,看起来你有一些凝灰岩比赛;)我非常感谢你的努力,所以这对我来说是一个upvote。
  • 正如anastaciu 所说,它比其他任何东西都更有趣。这绝对没什么好羞愧的。我们都互相学习;事情就是这样:)
  • 我实际上更喜欢这个答案,因为它更详细地解释了已删除方法的重载解决方案
猜你喜欢
  • 2012-07-21
  • 1970-01-01
  • 2019-07-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多