EC笔记:第二部分:11:在operator=中处理“自我赋值”
已经一年半没有写过博客了,最近发现学过的知识还是需要整理一下,为知笔记,要开始收费了以前写在为知笔记上笔记也会慢慢的转到博客里。
话不多说,进入正题。
考虑考虑以下场景:
当某个对象对自身赋值时,会出现什么现象??
例子:
#include <iostream>
class A {
private:
int *arr;
public:
A() {
arr = new int[256];
}
~A() {
delete arr;
}
const A& operator=(const A &other) {
delete arr; //清除原来的值
arr = new int[256]; //重新分配内存
std::memcpy(arr, other.arr, 256 * sizeof(int)); //赋值
return *this;
}
};
在这段代码中,类A管理了256个整数的数组,当发生赋值操作时,对象先将自身管理的内存区释放,然后重新分配内存并且赋值(这里可以直接进行内存拷贝,为了演示,做了删除并重新分配操作,假设这里是个vector,想象一下^_^)。这个实现在应对大多数情况是没有问题的。如:
int main() {
A a;
A b;
a = b;
}
这样完全没有问题。但是,假设出现以下场景:
int main() {
A a;
A &b = a;
//若干操作
a = b;
}
a和b表示的是同一个对象,那么在重新分配内存之前,就会将arr(a和b是同一个)指向的内存区域释放。然后在做memcpy的时候程序就会崩溃(引用了已释放的内存区域)。
重新对class A的operator=实现:
#include <iostream>
class A {
private:
int *arr;
public:
A() {
arr = new int[256];
}
~A() {
delete arr;
}
const A& operator=(const A &other) {
if(this == &other)
return *this;
delete arr; //清除原来的值
arr = new int[256]; //重新分配内存
std::memcpy(arr, other.arr, 256 * sizeof(int)); //赋值
return *this;
}
};
改进后,判断当前如果赋值和被赋值的是同一个对象,就直接返回,可以避免释放掉同一块内存。
这段代码虽然可以避免赋值上的问题,但是存在"异常安全性"的问题:试想,假设在new的时候抛出了一个异常(假设内存不足),那么,a在处理异常时,arr的状态就已经发生变化了。
另外,书中介绍了另一种避免赋值的时候释放掉有用内存的代码:
#include <iostream>
class A {
private:
int *arr;
public:
A() {
arr = new int[256];
}
~A() {
delete arr;
}
const A& operator=(const A &other) {
int *old_arr = arr;
arr = new int[256];
std::memcpy(arr, other.arr, 256 * sizeof(int));
delete old_arr;
return *this;
}
};
这段代码中,先对原有的arr做一个备份,然后使用other对新分配的内存进行更新,最后释放掉原来arr指向的内存区域。
即使没有"证同测试",这段代码也能正常工作,因为释放动作在赋值动作之后,这是后就真的存在两个副本了(如果*this和other指向不同的值,就是3个副本)。但是,这段代码显然在抛开"异常安全性"后在效率上比上面那段代码的效率低(即使两个对象指向同一内存,也要从新分配内存,并重新赋值)。所以,如果关心效率的话,应该在最前面增加"证同测试"。如下:
#include <iostream>
class A {
private:
int *arr;
public:
A() {
arr = new int[256];
}
~A() {
delete arr;
}
const A& operator=(const A &other) {
if (this==&other)
return *this;
int *old_arr = arr;
arr = new int[256];
std::memcpy(arr, other.arr, 256 * sizeof(int));
delete old_arr;
return *this;
}
};
至此,一份"异常安全的"且"效率优秀的"operator=操作符就完成了
请记住:
- 确保operator=有良好的行为。
- 当某个函数要操作同类对象或者多个继承自同一类对象时,不仅要考虑每个对象不同时的处理,还要考虑当某些对象是同一个对象的处理,确保正确性。