c++ 什么是移动语义?

我刚刚听完了关于C++0x的软件工程无线电podcast interview with Scott Meyers.大多数新功能对我有意义,我实际上对C 0x的兴奋,除了一个。我仍然没有得到移动语义…他们是什么?
最佳答案
我发现最容易理解移动语义与示例代码。让我们从一个非常简单的字符串类开始,它只保存一个指向堆分配的内存块的指针:

#include <cstring>
#include <algorithm>

class string
{
    char* data;

public:

    string(const char* p)
    {
        size_t size = strlen(p) + 1;
        data = new char[size];
        memcpy(data, p, size);
    }

由于我们选择自己管理内存,我们需要遵循rule of three.我将推迟编写赋值运算符,并且现在只实现析构函数和复制构造函数:

    ~string()
    {
        delete[] data;
    }

    string(const string& that)
    {
        size_t size = strlen(that.data) + 1;
        data = new char[size];
        memcpy(data, that.data, size);
    }

复制构造函数定义了复制字符串对象的含义。参数const string&绑定到所有允许您在以下示例中复制的字符串类型的表达式:

string a(x);                                    // Line 1
string b(x + y);                                // Line 2
string c(some_function_returning_a_string());   // Line 3

现在来看移动语义的关键洞察。注意,只有在第一行,我们复制x是这个深复制真的有必要,因为我们可能想检查x后,如果x以某种方式改变会非常惊讶。你注意到我只是说x三次(四次,如果你包括这句话),意味着每一次完全相同的对象?我们调用诸如x“lvalues”的表达式。

第2行和第3行中的参数不是左值,而是右值,因为底层字符串对象没有名称,因此客户端无法在稍后的时间点再次检查它们。
rvalues表示临时对象,在下一个分号(更准确地说:在完全表达式的末尾,词法包含右值)被销毁。这很重要,因为在b和c的初始化期间,我们可以使用源字符串做任何我们想要的,并且客户端不能告诉差别!

C 0x引入了一种称为“右值引用”的新机制,除其他外,
允许我们通过函数重载来检测右值参数。我们所要做的就是写一个带有右值引用参数的构造函数。在构造函数内部,我们可以使用源代码做任何我们想要的,只要我们将它保留在某个有效状态:

    string(string&& that)   // string&& is an rvalue reference to a string
    {
        data = that.data;
        that.data = nullptr;
    }

我们在这里做了什么?而不是深度复制堆数据,我们刚刚复制指针,然后将原始指针设置为null。实际上,我们已经“窃取”了原来属于源字符串的数据。同样,关键的见解是,在任何情况下,客户端都无法检测源是否已被修改。因为我们不是真的在这里做一个副本,我们称这个构造函数为“移动构造函数”。它的工作是将资源从一个对象移动到另一个对象,而不是复制它们。

恭喜,您现在了解移动语义的基础知识!让我们继续实现赋值运算符。如果你不熟悉copy and swap idiom,学习它​​并回来,因为它是一个真棒C成语与异常安全相关。

    string& operator=(string that)
    {
        std::swap(data, that.data);
        return *this;
    }
};

嗯,是吗? “右值引用在哪里?你可能会问。 “我们不需要它!是我的回答:)

注意,我们通过值传递参数,所以必须像任何其他字符串对象一样初始化。究竟如何初始化呢?在C++98的老式日子,答案应该是“通过复制构造函数”。在C 0x中,编译器根据赋值操作符的参数是一个左值还是右值,在复制构造函数和移动构造函数之间进行选择。

所以如果你说a = b,复制构造函数将初始化它(因为表达式b是一个左值),赋值操作符用新创建的深层副本交换内容。这是复制和交换惯用语的定义 – 制作副本,与副本交换内容,然后通过离开范围来摆脱副本。这里没有新的。

但是如果你说a = x y,移动构造函数将初始化它(因为表达式x y是一个右值),所以没有深层拷贝,只有一个有效的移动。
这仍然是一个独立的对象的论点,但它的结构是微不足道的,
因为堆数据不需要复制,只是移动。没有必要复制它,因为x y是一个右值,并且,可以从由rvalues表示的字符串对象移动。

总而言之,复制构造函数进行深层复制,因为源必须保持不变。
另一方面,移动构造函数只能复制指针,然后将源中的指针设置为null。以这种方式“取消”源对象是可以的,因为客户端没有办法再次检查对象。

我希望这个例子得到了重点。有很多更多的右值引用和移动语义,我故意留下来保持简单。如果您想了解更多详情,请参阅my supplementary answer

转载注明原文:c++ 什么是移动语义? - 代码日志