【问题标题】:Different constructor calls depending on compiler不同的构造函数调用取决于编译器
【发布时间】:2020-06-08 09:16:26
【问题描述】:

运行此问题中提供的代码:

Why move return an rvalue reference parameter need to wrap it with std::move()?

#include <string>
#include <iostream>
#include <utility>

template<typename T>
class TD;

class Widget {
public:
    explicit Widget(const std::string& name) : name(name) {
        std::cout << "Widget created with name: " << name << ".\n";
    }

    Widget(const Widget& w) : name(w.name) {
        std::cout << "Widget " << name << " just got copied.\n";
    }

    Widget(Widget&& w) : name(std::move(w.name)) {
        std::cout << "Widget " << name << " just got moved.\n";
    }

private:
    std::string name;
};

Widget passThroughMove(Widget&& w) {
    // TD<decltype(w)> wType;
    // TD<decltype(std::move(w))> mwType;
    return std::move(w);
}

Widget passThrough(Widget&& w) {
    return w;
}

int main() {
    Widget w1("w1");
    Widget w2("w2");

    Widget wt1 = passThroughMove(std::move(w1));
    Widget wt2 = passThrough(std::move(w2));

    return 0;
}

根据我使用的编译器产生不同的结果。使用最新的 Visual Studio 编译时(尝试了 C++14 和 C++17),我得到以下结果:

Widget created with name: w1.
Widget created with name: w2.
Widget w1 just got moved.
Widget w2 just got moved. //<---

在线运行并编译此代码,结果不同。在提供的问题中,用户也会收到相同的结果:

Widget created with name: w1.
Widget created with name: w2.
Widget w1 just got moved.
Widget w2 just got copied. //<---

为什么 w2 在使用 Visual Studio 时会被移动,而在使用各种不同的编译器时会被复制?

【问题讨论】:

  • 一个问题应该是自包含的。可以删除链接的问题。
  • 发生这种情况主要是因为 msvc(vs 默认使用的编译器)正在使用不同的实现,这些实现对于它的许多函数/类来说不是标准的。一个很好的例子是 std::exception,对于许多编译器来说,它是一个几乎不做任何事情的对象,但在 msvc 中,它可以将字符串作为其构造函数的参数,并且还有一个 .what() 函数允许打印它。我不确定这里是否也是这种情况,但很可能是。如果我错了,请纠正我,因为我也很好奇!
  • @GhostyFrosty 在这种特殊情况下,MSVC 从 MSVC v19.24 开始简单地击败了 Clang 和 GCC 来实现 P1825R0

标签: c++ visual-c++ rvalue-reference


【解决方案1】:

MSVC 已实现 P1825R0 - 更多隐式移动

从 Visual Studio C++ 文档的Microsoft C++ language conformance table 部分可以看出,这是 Visual Studio 实现的

截至Visual Studio 2019 version 16.4,编译器 MSVC 版本 19.24。

P1825R0 的核心部分已经添加到[class.copy.elision]/3 [emphasis mine]:

一个隐式可移动实体是一个自动存储的变量 非易失性对象或右值引用的持续时间 到非易失性对象类型。在下面的复制初始化中 在上下文中,可能会使用移动操作而不是副本 操作:

  • (3.1) 如果return ([stmt.return]) 或co_­return ([stmt.return.coroutine]) 语句中的表达式 是(可能 带括号的)id-expression 命名隐式可移动 在主体或 parameter-declaration-clause 中声明的实体 最内层的封闭函数或 lambda 表达式,或
  • (3.2) ...

重载分辨率为副本选择构造函数调用return_­value重载首先被执行,好像表达式或操作数是一个右值。 [...]。

使用godbolt.ms,以及以下人为的示例

#include <memory>

struct Foo {
    Foo(int num) : num(num) {}
    Foo(const Foo& f) : num(f.num) {}
    Foo(Foo&& f) : num(std::move(f.num)) {}
    Foo& operator=(const Foo&) = delete;
    Foo& operator=(Foo&&) = delete;
    int num;
};

Foo maybeConsumeMyPreciousFoo(Foo&& foo, bool consume_foo) {
    if (consume_foo) { return std::move(foo); }
    else { return foo; }  // Should not move.
}

我们可以特别检查maybeConsumeMyPreciousFoo(...) 函数中else { return foo; } 行的生成程序集;

对于 MSVC v19.23:

mov     rdx, QWORD PTR foo$[rsp]
mov     rcx, QWORD PTR __$ReturnUdt$[rsp]
call    Foo::Foo(Foo const &)             ; Foo::Foo
mov     rax, QWORD PTR __$ReturnUdt$[rsp]

对于 MSVC v19.24:

mov     rdx, QWORD PTR foo$[rsp]
mov     rcx, QWORD PTR __$ReturnUdt$[rsp]
call    Foo::Foo(Foo &&)       ; Foo::Foo
mov     rax, QWORD PTR __$ReturnUdt$[rsp]

分别表明,对于后一个版本,分支实际上从 foo 参数移动,就好像它是一个右值一样。


GCC 和 Clang 尚未实现 P1825R0

海合会:

C++ Standards Support in GCC:

[...]

C++2a 语言特性

[...]

DR:更多隐式移动(合并 P0527R1 和 P1155R3)

在 GCC 中可用吗?:否

叮当声:

C++ Support in Clang

[...]

C++20实现状态

P1825R0 甚至没有列出。

最后,cppreference's C++ compiler support page 还将 P1825R0 列为 Clang 和 GCC 都不支持。

【讨论】:

    猜你喜欢
    • 2020-11-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-02-01
    • 2019-04-16
    • 1970-01-01
    • 2017-11-25
    • 1970-01-01
    相关资源
    最近更新 更多