移动语义在这里如何工作?

How move semantics works here?

我有以下示例,但我不确定我是否完全理解移动语义逻辑:

#include <iostream>
#include <string>
#include <memory>

class Player
{
public:
    Player(std::string name)
        : m_name(std::move(name)) {}

private:
    std::string m_name;
};

int main()
{
    std::string s = "Zidane";
    std::cout << s << std::endl;

    Player player1(s); // not moved
    std::cout << s << std::endl; // s = "Zidane"

    Player player2(std::move(s)); // moved -> s is empty now
    std::cout << s << std::endl;

    return 0;
}

我的解释是,在第一种情况下(Player1),name 是类型 std::string 的左值,实际上是在 m_name 的构造函数之前被复制的调用,然后 std::move 作用于副本,所以最后副本为空,其内容已使用 move ctor 移动到 m_name。这就是为什么原始参数 s 保持不变的原因。正确吗?

在第二种情况下,不清楚:std::move 将左值参数转换为引用右值,然后发生了什么?在这种情况下,调用后参数 s 为空。

在调用 Player(std::string name) 之前,总是会为 'populate' name 参数创建一个新的 std::string

重载决议将决定是否调用 std::string 的复制构造函数或移动构造函数。

案例 1:Player player1(s);

新字符串由具有签名 basic_string( const basic_string& other ); 的复制构造函数创建,因为 's' 是一个左值。

您支付的操作总和为 1 次移动和 1 次字符串复制构造:

  1. 一份构造函数,用于构造函数的 name arg
  2. m_name class 成员
  3. 一步操作者

案例二:Player player2(std::move(s));

新字符串由 std::string 的移动构造函数创建,签名为 basic_string( basic_string&& other ) noexcept;

在第二种情况下,您调用 std::move,它将 s 转换为右值引用。 std::string 有 2 个构造函数,一个接受 const std::string&,另一个接受 std::string&&。一个右值引用可以绑定到一个左值引用和一个右值引用,但是右值引用版本是一个更好的匹配,所以它会被选中。

您支付的操作总和为字符串的 2 次移动构造:

  1. 一步构造函数,name arg 到构造函数
  2. m_name class 成员
  3. 一步操作者

请注意,正如@aschepler 和@underscore_d 所指出的,std::string 的移动构造函数不需要清除源字符串。人们不应该依赖于这种行为,因为它不能保证并且取决于字符串的移动构造函数是如何实现的。