为什么要在这里调用移动构造函数?

Why is the move constructor called here?

这是来自 C++ 测验的代码示例:

#include <iostream>
struct X {
    X(const char *) { std::cout << 1; }
    X(const X &) { std::cout << 2; }
    X(X &&) { std::cout << 3; }
};

X f(X a) {
    return a;
}

X g(const char * b) {
    X c(b);
    return c;
}

int main() {
    f("hello");
    g("hello");
}

程序的输出是什么?

我是这样认为的:

  1. f(X a)被调用,构造函数将const char*隐式转换为X,所以输出为1
  2. 由于我们没有对象来存储return值,return值被丢弃,没有输出
  3. g(const char*)被调用,X c(b)X(const char*)输出为1
  4. return值又被丢弃了一次 - 没有输出

所以答案是 11。测验的答案是 131。我用 g++ 4.4.4-13 得到的答案是 121。

据说这段代码是用这条命令编译的:

g++ -std=c++11 -Wall -Wextra -O -pthread

中间的数字从何而来?为什么可以是 3 或 2?

Copy elision 适用于 g 中的 return 语句,也可能适用于其他地方。引用自 cppreference:

Copy elision is the only allowed form of optimization that can change the observable side-effects. Because some compilers do not perform copy elision in every situation where it is allowed (e.g., in debug mode), programs that rely on the side-effects of copy/move constructors and destructors are not portable.

因此,对于您的示例代码,无法跨不同的实现可靠地预测输出。

理论上,这可以打印1311331313131331中的任何一个。作为一个测验问题,这很愚蠢。

  • f("hello");:

    • "hello" 通过转换构造函数转换为临时 X,打印 1.
    • 临时X用于初始化函数参数,调用移动构造函数,打印3。这可以省略。
    • x用于初始化临时return值,调用移动构造函数,打印3。这是一个函数参数,所以不允许省略,但是 return 是一个隐式移动。
  • g("hello");

    • "hello" 用于通过转换构造函数构造 c,打印 1.
    • c用于初始化临时return值,调用移动构造函数,打印3。这可以省略。

请记住,函数总是必须构造它们 return 的东西,即使它只是被调用代码丢弃。

至于打印 2,那是因为您使用的古老编译器没有实现 implicit-move-when-returning-a-local-variable 规则。