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 = std::strlen(p) + 1;
data = new char[size];
std::memcpy(data, p, size);
}
由于我们自己选择管理内存,我们需要遵循rule of three.我将推迟编写赋值运算符,现在只实现析构函数和复制构造函数:
~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&绑定到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,因为底层字符串对象没有名称,因此客户端无法在以后再次检查它们.
rvalues表示在下一个分号处被销毁的临时对象(更准确地说:在词法上包含rvalue的全表达式的末尾).这很重要,因为在b和c的初始化期间,我们可以用源字符串做任何我们想做的事情,而客户端无法区分!
C 0x引入了一种名为“rvalue reference”的新机制,其中包括:
允许我们通过函数重载检测rvalue参数.我们所要做的就是编写一个带有右值引用参数的构造函数.在构造函数内部,只要我们将它保留在某个有效状态,我们就可以对源执行任何操作:
string(string&& that) // string&& is an rvalue reference to a string
{
data = that.data;
that.data = nullptr;
}
我们在这做了什么?我们刚刚复制了指针,然后将原始指针设置为null(以防止源对象的析构函数中的’delete []’释放我们的’刚被盗的数据’),而不是深度复制堆数据.实际上,我们“窃取”了最初属于源字符串的数据.同样,关键的洞察力是在任何情况下客户都无法检测到源已被修改.由于我们在这里没有真正复制,我们将此构造函数称为“移动构造函数”.它的工作是将资源从一个对象移动到另一个对象而不是复制它们.
恭喜,您现在了解移动语义的基础知识!让我们继续实现赋值运算符.如果您对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是一个rvalue),所以不涉及深度复制,只有高效的移动.
这仍然是争论的一个独立对象,但它的构造是微不足道的,
由于堆数据不必复制,只需移动即可.没有必要复制它,因为x y是一个rvalue,同样,可以从rvalues表示的字符串对象移动.
总而言之,复制构造函数进行深层复制,因为源必须保持不变.
另一方面,移动构造函数可以只复制指针,然后将源中的指针设置为null.以这种方式“取消”源对象是可以的,因为客户端无法再次检查对象.
我希望这个例子得到了重点. rvalue引用和移动语义还有很多,我故意省略它以保持简单.如果您想了解更多详情,请参阅my supplementary answer.
标签:c,c11,move-semantics,c-faq 来源: https://codeday.me/bug/20190910/1802141.html