跳转至

90 std::move与移动赋值操作符

经过上一节89 Move Semantics in C++,已经基本了解移动语义的要点和所有基本知识了。移动语义能够将一个对象移动到另一个对象上,但是还没有涉及到其中的两个关键部分:std::movemove assignment oprator(移动赋值运算符),这是一个赋值操作符,当我们想把一个对象移动到一个已有的对象中时(不是构造一个新对象)。

1. std::move

上节的代码中,这里用std::move可以让我们把这个临时变量 name,转移到 m_Name 这个可以永久居住的地方(类成员),

    Entity(String&& name)
    //  :m_Name(name) {}
        :m_Name(std::move(name)) {}

新建一个 String:

String string = "Hello";
String dest = string; // 复制字符串到新变量中,并非移动

想要移动的话,显然需要 Entity 的移动构造函数,为了使用它我们需要确保传入的字符串变为临时的

String string = "Hello";
//  String dest = (String&&)string;
//  String dest((String&&)string);
// 这并非最优雅且对每个类型都使用的方法。因此可以使用一个灵活一些的函数,可以在编译时用auto找出输入的类型
String dest(std::move (string));

可以看到它返回一个右值引用类型,是以一种很好的模板化的方式来实现的,可以正确处理所有类型,包括常量等。

这里哪种方法都是创建一个新对象,因此会用移动构造函数,这就引出了移动赋值运算符

2. 移动赋值运算符

赋值操作符,只有当我们把一个变量赋值给一个已有变量时才会被调用,比如:

String dest(std::move (string));
dest = std::move(string);

运算符实际上就像一个函数,所以这里调用=运算符时,就像是你有一个 assign 函数 dest.assgin(std::move(string))、

移动赋值运算符长得很像移动构造函数:

// 移动赋值操作符
String& operator=(String&& other) noexcept
{
    printf("Moved!\n");

    // 检查self-assignment(自我赋值),确保不是将对象赋值给自身
    if (this != &other)
    {
        // 删除当前对象持有的资源
        delete[] m_Data; // **重点** 因为要覆盖原对象,删除内存防止内存泄漏

        // 将"other"对象的资源"窃取"到当前对象
        m_Size = other.m_Size;
        m_Data = other.m_Data;

        // 使"other"对象进入一个有效但未定义的状态
        other.m_Size = 0;
        other.m_Data = nullptr;
    }

    // 返回*this以支持链式赋值
    return *this;
}

调用测试:

int main()
{
    String apple = "Apple";
    String dest;

    std::cout << "Apple: ";
    apple.Print();
    std::cout << "Dest: ";
    dest.Print();

    dest = std::move(apple);

    std::cout << "Apple: ";
    apple.Print();
    std::cout << "Dest: ";
    dest.Print();

    std::cin.get();
}

可以看到 apple 的资源被 dest“偷走”了,我们基本上转移了整个字符数组的所有权,没有做任何复制或解除分配之类的事情。

总而言之,移动赋值操作符是你想要包含在类中的东西,当你包含一个移动构造函数时,因为可能会想要将一个对象移动到一个现有变量中。它基本上是五法则的一部分,五法则包含了新移动语义

C++三法则:如果需要析构函数,则一定需要拷贝构造函数和拷贝赋值操作符; C++五法则:为了支持移动语义,又增加了移动构造函数和移动赋值运算符。

std::move是你想要将一个对象转换为临时对象时要做的,换句话说如果你需要把一个已经存在的变量变为临时变量,你可以标记它,表示你可以从这个特定的变量中窃取资源,这使我们能够在现有的变量上执行移动通过操作。

要注意的是,std::move本身并不执行任何移动操作;它仅仅重新解释对象,使其可以被当作右值使用。实际的移动行为是由特定的移动构造函数或移动赋值操作符执行的,这些函数在接收到标记为右值的对象时被调用。"