什么是移动语义? [英] What are move semantics?

查看:128
本文介绍了什么是移动语义?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我刚刚听完了软件工程部的电台播客关于 C ++ 0x 的Scott Meyers访谈。大多数新功能对我来说很有意义,我现在对C ++ 0x感到兴奋,除了一个。我仍然没有得到移动语义 ...它们是什么?

I just finished listening to the Software Engineering radio podcast interview with Scott Meyers regarding C++0x. Most of the new features made sense to me, and I am actually excited about C++0x now, with the exception of one. I still don't get move semantics... What are they exactly?

推荐答案

它最容易理解移动语义与示例代码。让我们从一个非常简单的字符串类开始,它只保存一个指向堆分配的内存块的指针:

I find it easiest to understand move semantics with example code. Let's start with a very simple string class which only holds a pointer to a heap-allocated block of memory:

#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);
    }

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

Since we chose to manage the memory ourselves, we need to follow the rule of three. I am going to defer writing the assignment operator and only implement the destructor and the copy constructor for now:

    ~string()
    {
        delete[] data;
    }

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


$ b $ p

复制构造函数定义复制字符串对象的含义。参数 const string&

The copy constructor defines what it means to copy string objects. The parameter const string& that binds to all expressions of type string which allows you to make copies in the following examples:

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的表达式。

Now comes the key insight into move semantics. Note that only in the first line where we copy x is this deep copy really necessary, because we might want to inspect x later and would be very surprised if x had changed somehow. Did you notice how I just said x three times (four times if you include this sentence) and meant the exact same object every time? We call expressions such as x "lvalues".

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

The arguments in lines 2 and 3 are not lvalues, but rvalues, because the underlying string objects have no names, so the client has no way to inspect them again at a later point in time. rvalues denote temporary objects which are destroyed at the next semicolon (to be more precise: at the end of the full-expression that lexically contains the rvalue). This is important because during the initialization of b and c, we could do whatever we wanted with the source string, and the client couldn't tell a difference!

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

C++0x introduces a new mechanism called "rvalue reference" which, among other things, allows us to detect rvalue arguments via function overloading. All we have to do is write a constructor with an rvalue reference parameter. Inside that constructor we can do anything we want with the source, as long as we leave it in some valid state:

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

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

What have we done here? Instead of deeply copying the heap data, we have just copied the pointer and then set the original pointer to null. In effect, we have "stolen" the data that originally belonged to the source string. Again, the key insight is that under no circumstance could the client detect that the source had been modified. Since we don't really do a copy here, we call this constructor a "move constructor". Its job is to move resources from one object to another instead of copying them.

恭喜,您现在了解移动语义的基础知识!让我们继续实现赋值运算符。如果您不熟悉复制和交换惯用语,请了解一下并回来,因为它是一个非常棒的C ++成语与异常安全相关。

Congratulations, you now understand the basics of move semantics! Let's continue by implementing the assignment operator. If you're unfamiliar with the copy and swap idiom, learn it and come back, because it's an awesome C++ idiom related to exception safety.

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

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

Huh, that's it? "Where's the rvalue reference?" you might ask. "We don't need it here!" is my answer :)

请注意,我们传递参数 因此必须像任何其他字符串对象一样初始化。正确的是如何 要初始化?在旧版的 C ++ 98 中,答案应该是通过复制构造函数。在C ++ 0x中,编译器根据赋值运算符的参数是一个左值还是右值,在复制构造函数和move构造函数之间进行选择。

Note that we pass the parameter that by value, so that has to be initialized just like any other string object. Exactly how is that going to be initialized? In the olden days of C++98, the answer would have been "by the copy constructor". In C++0x, the compiler chooses between the copy constructor and the move constructor based on whether the argument to the assignment operator is an lvalue or an rvalue.

你说 a = b ,复制构造函数将初始化 code> b 是一个左值),赋值操作符用新创建的深层副本交换内容。这就是复制和交换习语的定义 - 制作副本,与副本交换内容,然后通过离开范围来摆脱副本。

So if you say a = b, the copy constructor will initialize that (because the expression b is an lvalue), and the assignment operator swaps the contents with a freshly created, deep copy. That is the very definition of the copy and swap idiom -- make a copy, swap the contents with the copy, and then get rid of the copy by leaving the scope. Nothing new here.

但是如果你说 a = x + y ,移动构造函数

But if you say a = x + y, the move constructor will initialize that (because the expression x + y is an rvalue), so there is no deep copy involved, only an efficient move. that is still an independent object from the argument, but its construction was trivial, since the heap data didn't have to be copied, just moved. It wasn't necessary to copy it because x + y is an rvalue, and again, it is okay to move from string objects denoted by rvalues.

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

To summarize, the copy constructor makes a deep copy, because the source must remain untouched. The move constructor, on the other hand, can just copy the pointer and then set the pointer in the source to null. It is okay to "nullify" the source object in this manner, because the client has no way of inspecting the object again.

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

I hope this example got the main point across. There is a lot more to rvalue references and move semantics which I intentionally left out to keep it simple. If you want more details please see my supplementary answer.

这篇关于什么是移动语义?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆