移动语义3: 移动赋值
题目描述
当我们用同类型的类类型变量进行赋值时,一般会调用复制赋值运算符重载,一般声明为T& operator=(const T& t)
,其中T
为该类类型。
现在有一个需要深复制的类String
,当我们用一个String
类型的变量赋值给一个String
类对象时,按照深复制的操作,需要额外分配内存,再将该值复制过去。
例如,我们执行String s; s = "abc";
这段代码时,会首先开辟一片内存,将"abc"
这一字符串字面量转换构造为String
类型。接着,再在s
中开辟一片内存空间,将"abc"
对应的那个String
类型变量的内容深复制过去。
我们会发现,这样操作会导致额外分配内存空间操作,造成浪费。因为给"abc"
这一字面量分配的那个String
类型变量会在完成赋值操作后析构从而被释放,这篇内存空间也不会再使用。而我们又可以发现,s
原先所指向的那一片内存空间所对应的内容将在赋值完成后不再被使用,所以我们可以通过“交换”s
和"abc"
对应的内存空间,在少分配1次新的内存空间的情况下同时完成“赋值”和“释放”原有空间的作用,就好像将原来的内容”移动“到了新的内存空间中。
我们分析一下何时才能使用上述操作,可以发现需要这一条件:赋值运算符右侧的变量将在赋值操作完成后立刻被释放,即赋值运算符右侧为右值表达式(字面量、a+b
等等)。而当右侧为左值表达式时,则不能采取这一交换手段。
因此,我们可以利用”移动赋值运算符“重载实现这一”交换“手段。
完善下列代码,实现这种"移动”语义。
关于输入
无
关于输出
见样例输出
参考答案
#include <cassert>
#include <iostream>
#include <cstring>
using std::cout, std::endl;
class String{
public:
int len;
char* str;
String(const char* x){
len = strlen(x);
str = new char[len + 1];
strcpy(str, x);
}
String& operator=(String&& x){
std::swap(len, x.len);
std::swap(str, x.str);
return *this;
}
String& operator=(const String& x){
if(this == &x) return *this;
delete[] str;
len = x.len;
str = new char[len + 1];
strcpy(str, x.str);
return *this;
}
//
~String(){
delete[] str;
}
};
int newCount{0};
int nowUsed{0};
//下列代码是内存分配计数器的实现,不用管
void* operator new[](std::size_t size) {
if (void* ptr = std::malloc(size)) {
newCount++;
nowUsed++;
return ptr;
}
throw std::bad_alloc();
}
void operator delete[](void* ptr) noexcept {
std::free(ptr);
nowUsed--;
}
int main(){
{
String a("Hello");
String b("World");
String c("");
c = String("Hi");
cout << c.str << endl;
c = a;
cout << c.str << endl;
cout << a.str << endl;
(c = a) = b;
cout << c.str << endl;
cout << b.str << endl;
(c = "Hi") = a;
cout << c.str << endl;
}
// assert: 若条件不满足,则运行时错误
// 整份代码应当分配 9 次内存,且不应内存泄漏
assert(newCount == 9);
assert(nowUsed == 0);
return 0;
}