使用 std::move 将参数传递给函数,如果将该参数声明为按值传递或使用移动操作数 &&,是否有区别?

Passing a parameter to a function with std::move, is there a difference if that parameter is declared as pass-by-value or with a move operand &&?

我很好奇这两者之间(对于编译器)是否有任何区别。它们之间唯一的区别是ValueClass在构造函数中通过引用

取参数
#include <string>

class ValueClass
{
public:
    ValueClass(std::string obj) : m_obj(std::move(obj)) {}

private:
    std::string m_obj;
};

class MoveClass
{
public:
    MoveClass(std::string&& obj) : m_obj(std::move(obj)) {}

private:
    std::string m_obj;
};

void SomeFunc()
{
    std::string a, b;

    a = "a";
    b = "b";

    ValueClass c(std::move(a));
    MoveClass d(std::move(b));
}

我快速检查了 goldbolt,确实看起来 MoveClass 构造函数调用导致了更少的汇编行(假设 godbolt 是准确的并且没有错误归因于任何汇编行) .

那么我的问题就变成了,为什么会有差异?编译器是否在将字符串移入并立即移出的地方设置了一些临时存储?

从理论上讲,这是应该优化掉的东西吗?在这种情况下,差异是否纯粹是为了让程序员为函数签名赋予更多意义和信息,以便任何可能想要调用它的人都更容易理解?

ValueClass 需要调用 std::string 的移动构造函数两次:一次创建 obj 参数,然后初始化 m_obj 成员。

MoveClass 只需要调用 std::string 的一个移动构造函数:obj 是对 b 的(右值)引用(没有新的 std::string对象在这里构造)。只有 m_obj 初始化涉及 std::string 移动构造函数。


是否允许省略任何这些调用(作为假设规则的例外)仍然存在问题。但事实并非如此;成员初始化和函数参数初始化都不是省略构造函数(或临时构造函数)的有效上下文:https://en.cppreference.com/w/cpp/language/copy_elision


现在,在给定的代码中,不一定存在需要编译器保留任何构造函数调用的副作用。事实上,如果您不在 main 中执行重新分配,gcc 会注意到整个程序没有副作用,可以优化为无操作。

https://godbolt.org/z/hxrVfC

您也没有提供 MSVC 正确的参数。最高优化是 /O2,而不是 -O3(尽管 -O2 确实有效)。注意编译器警告!