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

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

问题描述

我刚听完软件工程广播,内容涉及 C ++ 0x .大多数新功能对我来说都是有意义的,除了一个功能,我现在对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 is it 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 = std::strlen(p) + 1;
        data = new char[size];
        std::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 = std::strlen(that.data) + 1;
        data = new char[size];
        std::memcpy(data, that.data, size);
    }

复制构造函数定义复制字符串对象的含义.参数const string& that绑定到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的表达式称为左值".

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行中的参数不是左值,而是右值,因为基础字符串对象没有名称,因此客户端无法在以后的时间再次检查它们. 右值表示在下一个分号处销毁的临时对象(更精确地说:在词汇上包含右值的完整表达式的末尾).这很重要,因为在bc初始化期间,我们可以对源字符串进行任何所需的操作,并且客户端无法分辨出差异

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引入了一种称为右值引用"的新机制,其中包括: 允许我们通过函数重载来检测右值参数.我们要做的就是编写一个带有右值引用参数的构造函数.在该构造函数中,只要我们将其保持在 some 有效状态,就可以对源执行任何想要的操作:

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(以防止源对象的析构函数中的'delete []'释放我们的被盗数据").实际上,我们已经窃取"了最初属于源字符串的数据.同样,关键的见解是,在任何情况下客户都无法检测到源已被修改.由于我们实际上并未在此处进行复制,因此我们将此构造函数称为移动构造函数".它的工作是将资源从一个对象移动到另一个对象,而不是复制它们.

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 (to prevent 'delete[]' from source object's destructor from releasing our 'just stolen data'). 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 :)

请注意,我们通过值传递参数that ,因此that必须像其他任何字符串对象一样进行初始化.究竟that将如何初始化?在 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,则 copy构造函数将初始化that(因为表达式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,则 move构造函数将初始化that(因为表达式x + y是右值),因此不涉及深层复制,只有有效的移动. that仍然是该论点的独立对象,但其构造微不足道, 由于不必复制堆数据,因此只需移动即可.不必复制它,因为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天全站免登陆