问题

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



解决方法

我发现使用示例代码来理解move语义是最容易的.让我们从一个非常简单的字符串类开始,它只保存一个指向堆分配的内存块的指针:

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

由于我们选择自行管理记忆体,因此我们必须遵守规则的三个.我将推迟编写赋值运算符,并且现在只实现析构函数和复制构造函数:

    ~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&amp; 绑定到所有允许您在以下示例中进行复制的字符串类型的表达式:

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 的初始化期间,我们可以使用源字符串做任何我们想要的操作,客户端无法区分< / em>!

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

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

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

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

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

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

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

因此,如果你说 a = b ,则复制构造函数将初始化<是一个左值),赋值操作符用新创建的深层副本交换内容.这是复制和交换惯用语的定义 - 制作副本,与副本交换内容,然后通过离开范围来摆脱副本.这里没有新的东西.

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

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

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




相关问题推荐