不同的构造函数调用取决于编译器

Different constructor calls depending on compiler

运行 本题提供的代码:

#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. //<---

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

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[强调 我的]:

An implicitly movable entity is a variable of automatic storage duration that is either a non-volatile object or an rvalue reference to a non-volatile object type. In the following copy-initialization contexts, a move operation might be used instead of a copy operation:

  • (3.1) If the expression in a return ([stmt.return]) or co_­return ([stmt.return.coroutine]) statement is a (possibly parenthesized) id-expression that names an implicitly movable entity declared in the body or parameter-declaration-clause of the innermost enclosing function or lambda-expression, or
  • (3.2) ...

overload resolution to select the constructor for the copy or the return_­value overload to call is first performed as if the expression or operand were an rvalue. [...].

使用 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 Language Features

[...]

DR: More implicit moves (merge P0527R1 and P1155R3)

Available in GCC?: No

叮当声:

C++ Support in Clang

[...]

C++20 implementation status

P1825R0 not even listed.

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