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

15

这里是来自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. 由于没有对象来存储返回值,返回值被丢弃,没有输出
  3. 调用g(const char*),并且X c(b)X(const char*)相同,输出为1
  4. 返回值再次被丢弃,没有输出

因此答案是11。问题的给定答案是131。我使用g++ 4.4.4-13编译,得到的答案是121。

据说这段代码是使用以下命令编译的:

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

中间的数字是从哪里来的?为什么它可以是3或2?


复制省略(http://en.cppreference.com/w/cpp/language/copy_elision)不是强制性的。您不能依赖此优化。因此,返回语句输出为空不能被依赖。 - Lingxi
5
谁把这个写成了“测验”?这个程序有效地可以打印出四种不同的输出。 - T.C.
@T.C. 这是ACCU 2014年举办的一个名为C++ pub quiz的活动。 - DoctorMoisha
3
请记住,gcc 4.4是一个2009年的编译器,因此在返回时隐式移动还不存在。 - ColdCat
2
gcc 4.4怎么可能接受“-std=c++11”?它只知道“-std=c++0x”。 - Ruslan
@Ruslan 是的,我使用 -std=c++0x 编译并得到了 121。我认为题目所有者使用更新的 gcc 进行编译并得到了 131。 - DoctorMoisha
2个回答

17

理论上,这个函数可以打印出任何一个 1311331313131331。作为一道测验题目,这显然是很傻的。

  • f("hello");

    • 通过转换构造函数将 "hello" 转换成一个临时对象 X,打印出 1
    • 使用临时对象 X 初始化函数参数,调用移动构造函数,打印出 3。这个操作可能会被省略。
    • 使用变量 x 初始化临时返回值,调用移动构造函数,打印出 3。它是函数参数,所以无法省略,但返回值可以隐式移动。
  • g("hello");

    • 使用 "hello" 构造 c,打印出 1
    • 使用变量 c 初始化临时返回值,调用移动构造函数,打印出 3。这个操作可能会被省略。

请记住,函数总是需要构造它们返回的东西,即使它只是被调用代码所丢弃。

至于打印出 2,那是因为您使用的古老编译器没有实现返回本地变量时的隐式移动规则。


2
有没有什么原因必须使用临时变量(可能会省略)来初始化“f”的参数?为什么不能直接从参数初始化参数? - Angew is no longer proud of SO
1
@Angew 参数传递是复制初始化。(在[dcl.init]/17.6.2中插入长引用) - T.C.
1
谢谢,[dcl.init]/15 是我错过的。 - Angew is no longer proud of SO
@BЈовић 不,你不能在函数参数中使用NRVO。 - T.C.
@T.C. 有什么特殊的原因吗?我认为应该由编译器来应用 NRVO,但是它可以选择不这样做。 - BЈовић
显示剩余2条评论

3

复制省略适用于g中的return语句,可能也适用于其他地方。引自cppreference:

复制省略是唯一允许改变可观察副作用的优化形式。因为某些编译器并不在每个允许的情况下执行复制省略(例如,在调试模式下),依赖于复制/移动构造函数和析构函数的副作用的程序是不可移植的。

因此,在您的示例代码中,输出在不同的实现中无法可靠预测。


复制省略不适用于 f 中的返回。 - T.C.
@T.C. 糟糕,这是参数。 - Lingxi

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接