没有使用std::move的右值引用

8
我有以下的类
class widget {
// The methods only print their name, i.e. c'tor, destructor etc.
public:
    widget();
    widget(const widget&);
    widget(widget&&);
    ~widget();
    auto operator=(const widget&)  -> widget&;
    auto operator=(widget&&) -> widget&;
};

我在下面的代码中使用了它

#include "widget.h"

auto main() -> int {

    widget c(std::move(widget()));
    c = std::move(widget());

    return 0;
};

这里的行为对我来说很容易理解。在第一次调用中,构造了一个小部件(widget),然后调用移动构造函数,并在临时小部件上调用析构函数。

第二次调用做了同样的事情,但是调用的是移动赋值运算符而不是移动构造函数。离开主方法时,在c上调用了析构函数。


现在来到有趣的部分:

#include "widget.h"

auto main() -> int {

    widget c((widget()));
    c = widget();

    return 0;
};

如果省略对std::move的调用,第一种情况将停止工作,并且只会导致一个构造函数调用。而第二种情况仍然像以前一样工作。
我在gcc和clang上尝试过这个代码。为什么这两个函数调用会以不同的方式处理它们的参数?我错过了什么吗?

(widghet()) 是一个 prvalue,即 rvalue,因此如果可能的话它会被移动。std::move 并不会移动,它只是改变表达式的值类别。如果该表达式已经具有相应的值类别,则它是多余的。 - Columbo
1
括号是必要的,否则表达式会被评估为指向一个不带参数并返回“widget”的函数指针。 - mike
2
你的编译器是否正在优化某些结构并为你复制? - Maarten Bamelis
2
尝试使用 -fno-elide-constructors 进行编译。您的副本正在被省略(仅当 c 由不被引用指定的临时变量初始化时才有效)。 - David G
1个回答

12
widget()是一个纯右值(prvalue),所以在这一行中
widget c((widget())); // use widget c{widget()} or c{widget{}} for more clear code

它将被移动。不过,编译器只是执行复制/移动省略。加上-fno-elide-constructors编译选项,就可以看到所有的移动构造函数调用。

每当你显式使用std::move来移动一个prvalue时,你就不允许编译器执行省略;这就是为什么你在第一个片段中看到了移动构造函数的原因。这就是为什么尝试通过使用std::move作为返回值来“帮助”编译器几乎总是一个坏主意(除非你真的想返回一个右值引用)。


2
是的,额外的括号是必要的。否则,它就是一个函数声明,而不是对象定义。 - Benjamin Lindley
@BenjaminLindley 你说得对,我已经编辑了。由于某种原因,我错误地认为 widget 是一个对象,而不是类的名称。 - vsoftco

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