条款十一:在赋值运算符中处理”自我赋值“
自我赋值发生在对象给自己赋值的时候。
1 | class Widget{...}; |
这项操作没有什么意义,但是是合法的操作,在用户执行这项操作时要进行相关处理。
安全的实现自我赋值有两种方法(书上有三种,但是我觉得copy and swap又复杂又不好用,就记了两种):
证同测试(就是进行删除赋值操作前先判断要赋值的是不是其自身)
1
2
3
4
5
6
7
8Widget& Widget::operator=(const Widget& rhs)
{
if(this == &rhs) return *this;
delete _pb; //_pb是Widget的一个数据成员
_pb = new Bitmap(*rhs.pb);
return *this;
}用一个额外的变量暂时存储_pb原来的值
1
2
3
4
5
6
7Widget& Widget::operator=(const Widget& rhs)
{
Bitmap* pOrig = _pb;
_pb = new Bitmap(*rhs.pb);
delete pOrig;
return *this;
}
- 确保对象的自我赋值是安全的,上述两种方法。
- 操作多个对象的函数,多个对象是同一个对象时,保证函数的行为仍然正确。
条款十二:复制对象时复制所有成分
当自己定义了赋值运算符时,要注意使赋值运算符能拷贝类内所有成员。
一个典型的没有全部拷贝例子:
1 | class Customer{ |
很明显,都没有对_lastTransaction这一成员进行复制,但编译器不会提醒你这一问题,修改类时注意同时修改赋值运算符的行为。
在继承的时候,会发生比较严重的问题。
1 | class PriorityCustomer : public Customer{ |
- 赋值运算符或者拷贝构造函数应保证复制对象内的所有变量和其父类成分。
- 如果赋值运算符和拷贝构造函数有相近的代码,不要互相调用,要写一个
private的init()
函数来降低代码重复率。
条款十三:用对象管理资源
例子是我们正在实现一个表示投资的类。
1 | class Investment{...} //一个Investment类 |
f()
看起来没有什么问题,但如果在...
中出现了return
语句等,控制流就到达不了delete
语句。
为确保createInvestment()
返回的资源总是可以被释放,需要将资源放入对象内,当离开f,对象进行析构,就可以对资源进行回收。
这时会用到智能指针,并且最好是用shared_ptr
,其在指向的对象引用计数为0时会自动对其析构。
1 | void f() |
有两个比较关键的思想:
- 获得资源后立即放进对象内,即资源取得即初始化(RAII)
- 管理资源的对象可以确保资源被释放(上面例子中当pInv的生命周期结束时,会自动将资源释放)
不用auto_ptr
是因为一般都不用,这个东西设计有问题。
但是还要注意一个问题,智能指针并不能释放数组,因为他对其所指向对象执行的是delete
而不是delete[]
,所以对于动态分配的数组要自己定义对象进行管理。
- 使用RAII对象,
shared_ptr
是一个可以的选择。
条款十四:资源管理类中注意处理复制行为的发生
上一条给出了RAII的概念,但上面着重讲了怎样管理在堆上申请的资源,对于一些其他的资源,我们需要创建自己的资源管理类来进行管理。如:mutex互斥锁变量,socket网络套接字等。
为管理一个C API中的一个类型为Mutex
的互斥锁对象,我们可以创建一个这样的RAII 类:
1 | class Lock{ |
但是当Lock m1(&m); Lock m2(m1);
时怎么处理。
通常用两种方式处理RAII对象的复制操作:
- 禁止复制,条款6(继承Uncopyable)
- 对底层资源增加引用计数,类似于
shared_ptr
的做法
使用shared_ptr
的版本:
1 | class Lock{ |
- 赋值RAII对象必须同时赋值它所管理的资源,资源的copying行为决定RAII对象的copying行为
- 一般对RAII类的copying行为进行:禁止复制/使用引用计数法两种操作
条款十五:在资源管理类中提供访问原始资源的接口
前面我们介绍了资源管理类,其主要作用是防止资源泄漏。在用户和原始资源之间产生一个中介。但是,有许多的API的的使用需要访问原始资源。这时资源管理类需要提供接口使用户可以访问这些原始资源。
举个例子:
1 | //条款13中的createInvestment() |
通常,对于RAII对象,要有一个get方法,获得原始资源。
shared_ptr重载了operator->和operator*,使用这两个操作符的时候,shared_ptr对象会隐式转换为原始指针。
还有一个通过资源管理类获得原始资源的方法是提供隐式转换函数:
1 | class Font{ |
- 许多API都需要访问原始资源,所以RAII类要提供访问原始资源的接口。
- 显示转换比较安全但是使用较麻烦,隐式转换使用方便但容易造成错误。