第十四章 近乎自动地管理内存
对指针进行编程充满了可能引发错误的隐患。很多指针引发的问题都是因为不同的指针指向的对象之间不是互相独立的,从而导致错误:
- 复制一个指针不会导致对指针所指的对象的复制,当无意中使两个指针指向同一个对象时,常常会导致产生其妙的错误。
- 删除一个指针不会释放指针所指对象所占用的内存,这常常导致内存泄漏。
- 删除一个对象但是没有删除指向该对象的指针会产生一个空悬指针(dangling pointer),在程序中使用这些指针的时候会导致未定义操作。
- 如果定义一个指针但是不对它进行初始化,会使该指针没有指向任何地方,这时候如果程序中用到该指针,会导致未定义操作。
我们希望我们的类与它所操作的对象的类之间彼此独立,所以这个类必须是一个模板。因为我们希望用它来封装句柄行为,所以我们把这个类叫做Handle。在这个类中将具有以下几点性质:
- 一个Handle类对象是一个指向某对象的值。
- 我们可以对Handle类型对象进行复制。
- 我们可以通过检测一个Handle类型对象来判断它是否指向另一个对象。
- 如果一个Handle类型对象指向继承关系树中某个类的一个对象,我们可以用Handle类对象来触发多态行为。也就是说,如果通过Handle类来调用一个虚拟函数,我们希望程序在运行的时候能动态地选择调用哪个函数,就像是我们在通过一个真实的指针调用这个虚拟函数那样。
我们的Handle类将会有一个规定的接口:一旦你使一个Handle类型对象指向一个对象,这个Handle类型对象将负责为那个对象进行内存管理。对一个对象我们只能为其匹配一个Handle类型对象,在此之后就不应该在随后直接通过指针来访问该对象;所有的访问都要通过这个Handle类型对象来进行。这一限制使得Handle避免了使用C++自带的指针的时候固有的问题。在复制一个Handle类型对象的时候,我们为该对象生成了一个新的复件,这样每个Handle类型对象都指向各自的复件。在删除一个Handle类型对象的时候,它会删除相应的对象,而这也是删除该对象的唯一途径。我们还允许用户生成一个没有指向任何对象的Handle类型对象,不过用户试图通过这样的一个对象来访问它所指向的对象时会抛出一个异常。用户可以通过检测Handle是否有效来避免抛出这个异常。
template <class T> class Handle{
public:
Handle():p(0){
}
Handle(const Handle& s):p(0){
if(s.p) p = s.p->clone();}
Handle& operator=(const Handle&);
~Handle(){
delete p;}
Handle(T* t):p(t){
}
operator bool() const {
return p;}
T& operator*() const;
T operator->() const;
private:
T* p;
};
赋值运算符函数和复制构造函数一样,也是(根据条件判断)调用clone函数来生成对象的一个新的复件:
template<class T>
Handle<T>& Handle<T>::operator=(const Handle& rhs)
{
if(&rhs != this){
delete p;
p = rhs.p ? rhs.p->clone() : 0;
}
return *this;
}
operator bool()
检测一个Handle类型对象的值。在Handle类型对象与一个实际的对象关联的时候返回真值。
template<class T>
T& Handle<T>::operator*() const
{
if(p)
return *p;
throw runtime_error("unbound Handle");
}
template<class T>
T* Handle<T>::operator->() const
{
if(p)
return p;
throw runtime_error("unbound Handle");
}
*student
将得到与*(student.p)
相同的结果。
C++语法要求我们定义一个->运算符来返回一个可以看作是指针的值。在定义operator->函数的时候,如果x是定义operator->函数的类的一个对象,那么x->y
就等同于(x.operator->())->y
在这里,operator->
函数返回x对象封装的指针。因此,对于一个students对象,student->y
就等同于(student.operator->())->y
根据我们对operator->()
函数的定义,上式又等同于student.p->y
我们可以用Handle类重写13.3.1中基于指针的成绩程序
int main()
{
vector<Handle<Core>> students; //改变其类型
Handle <Core> record; //改变其类型
char ch;
string::s