【问题标题】:Why is the move constructor called here?为什么在这里调用移动构造函数?
【发布时间】:2015-11-04 16:11:27
【问题描述】:

这是一个 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?

【问题讨论】:

  • 复制省略 (en.cppreference.com/w/cpp/language/copy_elision) 不是强制性的。您不能依赖此优化。所以 return 语句输出什么都不能依赖。
  • 谁写了这个作为“测验”?这个程序可以有效地打印四种不同的输出。
  • @T.C.这是 ACCU 2014 年的 C++ pub quiz
  • 不要忘记 gcc 4.4 是 2009 年的编译器,所以还没有隐式返回。
  • gcc 4.4 怎么能接受-std=c++11?它唯一知道的是-std=c++0x

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


【解决方案1】:

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

  • f("hello");:

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

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

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

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

【讨论】:

  • 是否有理由为什么必须使用(并且可能省略)临时变量来初始化f 的参数?为什么不能直接从参数初始化参数?
  • @Angew 参数传递是复制初始化。 (在此处插入来自 [dcl.init]/17.6.2 的冗长引用)
  • 谢谢,[dcl.init]/15 是我错过的。
  • 11 也可以是结果,如果编译器执行 NRVO。还是我错了?
  • @BЈовић 不,您不能使用函数参数进行 NRVO。
【解决方案2】:

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

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

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

【讨论】:

  • 复制省略不适用于f中的退货。
  • @T.C.糟糕,是参数。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2017-04-07
  • 1970-01-01
  • 2023-03-18
  • 2013-04-25
  • 1970-01-01
  • 2013-10-01
  • 2012-04-29
相关资源
最近更新 更多