【问题标题】:Why is the move constructor invoked only once?为什么移动构造函数只调用一次?
【发布时间】:2018-12-12 06:22:43
【问题描述】:

我有以下功能:

SomeClass func()
{
    SomeClass someObject;
    someObject.mutate("some text");
    return someObject;
}

还有以下两种情况:

1

int main()
{
    func();
    return 0;
}

2

int main()
{
    SomeClass someObject = func();
    return 0;
}

我已关闭 NRVO,不会发生复制/移动省略。

在这两种情况下,我都有相同的输出:

"default constructor"
"move constructor"

为什么在情况 2 中移动构造函数只调用一次?我本来希望为函数的返回值调用一次,然后为初始化 someObject 变量调用一次。

更新:更清楚地说:输出用于调试构建。对于发布版本,我只有“默认构造函数”,由于复制/移动省略,这对我来说似乎很清楚。我想了解调试版本的不同输出。

【问题讨论】:

  • 你是用 C++17 构建的吗?如果 NRVO 启用,您根本不会看到移动 c'tor 调用。
  • 这是一个没有根据的假设。即使在调试版本中,编译器仍然会进行很多优化。主要是因为它们不会影响可调试性。
  • 这并不奇怪。我询问了您的构建选项(一种语言标准)是有原因的。编译器可以并且将进行的优化类型取决于很多 因素。你不能把它简化为 debug vs release。
  • 老实说,如果调试版本决定在发布版本上关闭复制省略,我会感到非常惊讶。移动和复制构造函数可能会产生副作用,因此可能会导致代码行为不同。
  • g++ -std=c++14 -fno-elide-constructors 给我default+move+move。切换到c++17 会得到default+move。删除 -fno-elide-constructors 导致 default 仅适用于 c++14 和 17。

标签: c++ constructor move


【解决方案1】:

这是由于 C++17 中的复制省略所致。来自cppreference:

在以下情况下,编译器是允许的,但不需要省略类对象的复制和移动(C++11 起)构造,即使复制/移动(C++11 起)构造函数和析构函数也是如此有明显的副作用。
[...]
在对象的初始化中,当源对象是无名临时对象并且与目标对象具有相同的类类型(忽略 cv-qualification)时。当无名临时变量是 return 语句的操作数时,这种复制省略的变体称为 RVO,“返回值优化”。
这种优化是强制性的;往上看。 (C++17 起)

另请参阅复制初始化。同样来自cppreference:

复制初始化的效果是:
首先,如果 T 是类类型并且初始化程序是纯右值表达式,其 cv 非限定类型与 T 相同,则初始化程序表达式本身,而不是从它的临时物化,用于初始化目标对象:请参阅副本省略 (C++17 起)

请注意,这不是 NRVO(命名返回值优化)。这是 RVO。

在 C++14 中,如果您不希望优化不会发生(请参阅 -fno-elide-constructors)。
Demo(使用 GCC,但 Clang 产生相同的结果)

在 C++17 中,这是强制性的,所以它确实发生了。
Demo(同样,GCC 但 Clang 同意)

【讨论】:

  • 是的,我同意你的看法。似乎 VS2017 和 GCC 的行为不同。 GCC 只输出“默认构造函数”(应该是这样),而 VS2017 输出“默认构造函数”+“移动构造函数”。
  • @JollyRoger 可能是因为它没有实现强制复制省略。请参阅this post,它看起来像是在更新的版本中实现的。也许更新你的编译器?
  • 但是在所有情况下你仍然有一个动作。我对 GCC 没有任何动作,只有在某些情况下使用 VS2017。出了点问题。
  • 我预计复制/移动省略活动不会移动。
  • -fno-elide-constructors 停用 GCC 和 Clang 的复制省略。没有它,你当然没有任何动作。
【解决方案2】:

因此,应该发生两种省略情况:一种用于 return 语句,另一种用于初始化。没有任何省略,输出应该是:默认构造函数,移动构造函数,移动构造函数。一些编译器提供了禁用省略的选项。使用带有 -fno-elide-constructors 的 GCC 进行测试,并确认了此行为。

现在,启用省略后,行为尤其取决于使用的 C++ 标准 (C++11/C++14/C++17),其次它似乎也取决于编译器。从 C++11 开始出现省略案例,从 C++14 开始出现其他情况,从 C++17 开始出现其他情况。

在案例 2 中,GCC C++14(和 C++11)应用了两种省略,而 MSVC C++14 编译器只应用了一种(尚未确定是哪一种)。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2023-03-18
    • 2013-12-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-07-05
    相关资源
    最近更新 更多