我们先来看看类中有指针成员可能会有什么不良的影响。
class HasPtr
{
private:
int *ptr;
int val;
public:
HasPtr(int *p, int i):ptr(p), val(i){}
int *get_ptr() const {return ptr;}
int get_int() const{return val;}
void set_ptr(int *p){ptr = p;}
void set_int(int i){val = i;}
int get_ptr_val() const{return *ptr;}
void set_ptr_val(int val){*ptr = val;}
};
int main()
{
int *ip = new int(42);
HasPtr ptr(ip, 10);
delete ip;
cout<<ptr.get_ptr_val()<<endl;
return 0;
}
对象中的ptr指针成了悬垂指针,指向了一个不复存在的对象
解决方案一:使用智能指针
常规指针行为的危险主要是因为多个对象中的指针成员同时指向一个共享对象,当该共享对象的内存被释放时,对象 中的指针成员变成悬垂指针,指向一个不负存在的对象。然而这种错误往往是难以发现的。
引入使用计数 & 使用计数类
智能指针一般都是通过定义一个使用计数(use count)来实现的,它和被共享的对象相关联,而用户定义的类对象是 通过使用计数类间接和共享对象联系的。
我们假设有甲、乙、丙三个人,甲是我们定义的对象ptr,丙是共享对象ip,而甲是通过乙来对丙进行读写的,并且 乙还知道有多少个人在读写丙。那么乙就是所谓的引用计数类了。
class U_Ptr
{
//引用计数类
//我们不希望普通用户使用U_Ptr类
//所以它不存在任何public成员,并
//将HasPtr设置成友元
friend class HasPtr;
int *ip;
size_t use;
U_Ptr(int *p):ip(p), use(1){ }
~U_Ptr() { delete ip; }
};
class HasPtr
{
private:
U_Ptr *ptr;
int val;
public:
HasPtr(int *p, int i):ptr(new U_Ptr(p)), val(i){ }
//复制类中的成员以及使引用计数加1
HasPtr(const HasPtr &orig):
ptr(orig.ptr), val(orig.val) { ++ptr->use; }
//如果计数器为0,那么就delete U_Ptr对象
~HasPtr() { if(--ptr->use == 0) delete ptr; }
int *get_ptr() const {return ptr->ip;}
int get_int() const{return val;}
void set_ptr(int *p){ptr->ip = p;}
void set_int(int i){val = i;}
int get_ptr_val() const{return *ptr->ip;}
void set_ptr_val(int val){*ptr->ip = val;}
};
这样我们就无需担心会有悬垂指针的问题了,但还有一个问题就是多个类成员指针因为指向同一个基础对象而纠缠在一起。解决方案二:定义值型类
class HasPtr
{
private:
int *ptr;
int val;
public:
HasPtr(const int &p, int i):ptr(new int(p)), val(i) { }
//定义复制构造函数,即深拷贝构造函数,重新分配新的内存单元
HasPtr(const HasPtr &orig):
ptr(new int (*orig.ptr)), val(orig.val){ }
~HasPtr() { delete ptr; }
int get_ptr_val() const {return *ptr;}
int get_int(int i) {return val;}
};
关键是上述代码中的复制构造函数,值型类与引用计数类的本质区别是:值型类改变的是指针所指向的值,而不是指针。