当设计含有指针的类时,应特别小心。因为指针指向的对象通常不包含在类的对象中,当类对象进行复制时,默认复制构造函数通常是浅拷贝(bitwise copy),只是拷贝了指针的值,这是两个指针指向同一个对象。通过其中一个指针就可以改变对象的值,也可以释放指针指向的对象;这时候另一个指针并不知情。引用C++ Primer的例子
class HasPtr{
public:
HasPtr(int *p, int i):ptr(p),val(i){}
//获取成员变量的接口
int *get_ptr()const { return ptr;}
int get_int const {return val;}
//设置成员变量的接口
void set_per(int *p){ptr=p;}
void set_int(int i){val=i;}
int get_ptr_val()const {return *ptr;}
int set_ptr_val(int val)const {*prt=val;}//指针的值没变,所以可以为const
private:
int *ptr;
int val;
}
按照如下使用:
int *ip=new int(42);
HasPtr prt1(ip,42);
HasPtr ptr2(ptr1);//bitwise copy
这时ptr1和ptr2中的指针指向同一个对象。假如有一个对象释放,运行析构函数(如果有的话),另一个对象中的指针将成为悬浮指针,而这个对象对此毫不知情。
为了避免类似的事情发生,C++中给我们提供了智能指针。智能指针的本质是使用一个类来管理指针。在这个类里面加了一个变量--引用计数,记录与指针对象关联的类的个数。只有当引用计数为零时,才可以释放指针指向的对象。智能指针的行为要和普通指针一样。但是智能指针多了引用计数,当引用指针类的个数发现变化时(比如复制构造函数和复制操作符),引用计数要体现出这样变换。例如,在复制构造函数中,引用计数应加1。而在赋值操作符(重载)中,左操作数使用的智能指针引用计数减1 ,而右操作数使用的智能指针引用计数加1。下一个问题就是要把这个引用计数放到哪里?可以放到类HasPtr中吗?如果放到HasPtr中
class HasPtr{
……
private:
int *ptr;int val;
size_t use;//引用计数
}
这样每个对象中就会有引用计数,当我们更新引用计数时,要更新每个对象中的use值,实在难以实现。但是真的不能放到HasPtr中吗?只要确保每个对象都能知道引用计数的值,有能确保引用计数容易更新,就可以。方法是使用指针(指针型句柄就是这样做的)。
class HasPtr{
……
private:
int *ptr;
int val;
size_t *use;//引用计数
}
class U_Ptr{
friend class HasPtr;
int *ip;
size_t use;
U_Ptr(int *P):ip(p),use(1){}
~U_Ptr(){ delete ip;}
}
HasPtr为友元。计数器类中的变量都为private。
使用智能指针类,要像使用普通指针一样,所以要重新设计HasPtr类
class HasPtr{
public:
HasPtr(int *p, int i):ptr(new U_Ptr(p)),val(i){}
//重新实现复制否造函数
HasPtr(const HasPtr &orig):
ptr(orig.ptr),val(orig.val){++ptr->use;}
HasPtr&operator=(const HasPtr& rhs);
//获取成员变量的接口
int *get_ptr()const { return ptr->ip;}
int get_int const {return val;}
//设置成员变量的接口
void set_per(int *p){ptr->ip=p;}
void set_int(int i){val=i;}
int get_ptr_val()const {return *ptr->ip;}
int set_ptr_val(int val)const {*prt->ip=val;}//指针的值没变,所以可以为const
private:
U_Ptr *ptr;
int val;
}
//实现赋值操作符重载
HasPtr& HasPtr::operator=(const HasPtr &rhs)
{
++rhs.prt->use++;//右操作数智能指针的引用计数加1
if(--ptr->use==0)//左操作数智能指针的引用计数减1
delete ptr;
ptr=rhs.ptr;
val=rhs.val;
return *this;
}