前言:浅拷贝容易出现对同一内存空间进行2次撤销,造成程序崩溃。于是,我们可以利用指针智能来解决这一问题。本节主要介绍利用使用计数类构造智能指针类。
使用计数: 智能指针将一个计数器与类指向的对象相关联。使用计数跟踪该类有多少个对象共享同一指针。使用计数为0时,删除对象。
规则:
1、每次创建类的新对象时,初始化指针并将计数器置1
(计数器置1的原因是在执行析构函数时,计数器先减1,然后再判断是否为0进行内存撤销,假设创建了一个新对象不进行任何操作直接进行析构,则减1载判断是否为0,那么计数器初始值就必须为1才能内存撤销。)
2、当对象作为另一个对象的副本而创建时,复制构造函数复制指针并增加与之相应的使用计数的值
(对象增加,用于跟踪该类有多少个对象的引用计数当然要随之增加)
3、对一个对象进行赋值时,赋值操作符减少左操作所指对象的使用计数值(若使用计数减至为0,则删除对象),并增加右操作数所指对象的使用计数的值。
(减少左操作数所指对象的使用计数,是因为左操作由原来指向a对象改为指向b对象,那么共享a的对象的数目自然要减少,同理b对象的共享对象的数目要相应增加。假设是同一对象的自我赋值操作,也满足上述原理。)
例1:不同对象的赋值
int *p = new int(7);
int *q = new int(8);
SmartPtr<int> SP(p, 0);
cout << "SP对象原始指向的对象:"<< SP.get_ptr_val() << endl;
SmartPtr<int> SP1(q, 0);
SP = SP1;
cout << "SP = SP1后指向的对象:" << SP.get_ptr_val() << endl;
例2:同一对象的自我赋值
4、调用析构函数时,减少使用计数的值,若计数减至0,则删除基础对象
(析构表明该类资源被释放,则指向基础对象的共享对象少了一个,使用计数当然减1了)
实现方式一: 定义一个单独的使用计数类
template<class T> class SmartPtr;
//引用计数类
template<class T>
class U_Ptr
{
friend class SmartPtr<T>;
U_Ptr(T *p) : ip(p), use(1) { }
~U_Ptr() { delete ip;}
private:
T *ip; //指向共享对象的指针
size_t use; //引用计数
};
——类的所有成员均为private,是不希望普通用户可以使用U_Ptr类,将SmartPtr类设置为友元,是其可以访问 U_Ptr类。
——友元模板类的使用规则
方式一:在类之前声明模板类
template<class T> class B;
tempate<class T> A
{
friend class B<T>;
};
方式二:在类中说明类为模板类
template<class T>
class A
{
tempalte<class T> friend class B;
};
使用计数工作原理图:
1、U_Ptr类保存指针和使用计数,每个SmartPtr对象指向一个U_Ptr对象
2、使用计数跟踪指向每个U_Ptr对象的SmartPtr对象的数目。
智能指针类SmartPtr
/智能指针类
template<class T>
class SmartPtr
{
public:
SmartPtr(T *p, T i) : ptr(new U_Ptr<T>(p)), val(i) { }
SmartPtr(const SmartPtr &orig) : ptr(orig.ptr), val(orig.val) { ++ptr->use; } //复制构造函数,使用计数加1
SmartPtr<T>& operator=(const SmartPtr& rhs);
~SmartPtr() {
if (--ptr->use == 0)
delete ptr;
}
public:
T get_ptr_val() const
{
int a = *ptr->ip;
return *ptr->ip;
}
void set_ptr_val(T i){ *ptr->ip = i; }
private:
U_Ptr<T> *ptr; //指向U_ptr对象的指针
T val;
};
template<class T>
SmartPtr<T>& SmartPtr<T>::operator=(const SmartPtr& rhs)
{
++rhs.ptr->use; //右操作数使用计数加1
if(--ptr->use == 0) //左操作数使用计数减1
{
delete ptr;
}
ptr = rhs.ptr;
val = rhs.val;
return *this;
}
—— ptr(new U_Ptr<T>(p))
使用指针形参p创建一个新的U_Ptr对象,其中p为SmartPtr共享的基础对象指针
——SmartPtr(const SmartPtr &orig)
复制构造函数
——SmartPtr<T>& operator=
赋值操作符
——return *this;
this为指向该类或对象的指针,故*this表示为该对象
——复制控制成员:复制构造函数、赋值操作符、析构函数
——三法则:如果一个类需要析构函数,则该类几乎也必然需要定义自己的复制构造函数和赋值操作符
——无论类是否定义了自己的析构函数,都会创建和运行合成析构函数。如果类定义了析构函数,则类定义的析构函数结束之后运行合成析构函数。
注意要点:
~U_Ptr() { delete ip;}
析构函数中,使用delete对ip进行销毁,表明ip指向一个动态分配的空间,而ip的初始化由ptr(new U_Ptr<T>(p))
完成,表明p指向的内存空间必须是动态分配的。
1)p指向的内存空间是动态分配的,
int *p = new int(7);
SmartPtr<int> SP(p, 0);
cout << SP.get_ptr_val() << endl;
2、p指向的空间是静态分配的
int a = 7;
int *p = &a;
SmartPtr<int> SP(p, 0);
cout << SP.get_ptr_val() << endl;
这是因为int a=7;
的内存空间是编译器在栈区中自动分配的,由编译器自动完成分配和回收操作,无需程序员delete操作。此时ip恰好指向a变量的内存地址,U_Ptr在析构时delete ip就会出现错误。
若修改U_Ptr的析构函数为~U_Ptr() { /*delete ip;*/}
则运行正常。
由图可知,ip与p存储的地址值相同,即共同指向基础对象。use = 2 表明有2个共享基础对象的SmartPtr对象,与程序中创建一个新对象SP,加上用SP复制的一个SP1对象的数目吻合。