【C++11】指针引用计数技术及智能指针的简单实现(共享指针是怎样计数的)?
目录
计数原理
智能指针将一个计数器与类指向的对象相关联,引用计数跟踪共有多少个类对象(shared_ptr对象?)共享同一指针。它的具体做法如下:
1、当创建类的新对象时,初始化指针,并将引用计数设置为1
2、当对象作为另一个对象的副本时,复制构造函数复制副本指针,并增加与指针相应的引用计数(加1)
3、使用赋值操作符对一个对象进行赋值时,处理复杂一点:
(赋值判断要先判断 是否是自己给自己赋值)
先使左操作数的指针的引用计数减1(为何减1:因为指针已经指向别的地方),如果减1后引用计数为0,则释放指针所指对象内存。
然后增加右操作数所指对象的引用计数(为何增加:因为此时做操作数指向对象即右操作数指向对象)。
析构函数:调用析构函数时,析构函数先使引用计数减1,如果减至0则delete对象。
智能指针的简单实现
1.智能指针是什么
简单来说,智能指针是一个类,它对普通指针进行封装,使智能指针类对象具有普通指针类型一样的操作。具体而言,复制对象时,副本和原对象都指向同一存储区域,如果通过一个副本改变其所指的值,则通过另一对象访问的值也会改变.所不同的是,智能指针能够对内存进行进行自动管理,避免出现悬垂指针等情况。
2.实现
了解了引用计数,我们可以使用它来写我们的智能指针类了。智能指针的实现策略有两种:辅助类与句柄类。这里介绍辅助类的实现方法。
4.1.基础对象类
首先,我们来定义一个基础对象类(基础对象类:int、string、自定义的Ponit类等)Point类,为了方便后面我们验证智能指针是否有效,我们为Point类创建如下接口:
class Point
{
public:
Point(int xVal = 0, int yVal = 0) :x(xVal), y(yVal) { }
int getX() const { return x; }
int getY() const { return y; }
void setX(int xVal) { x = xVal; }
void setY(int yVal) { y = yVal; }
private:
int x, y;
};
4.2.辅助类(指针和计数管理)
在创建智能指针类之前,我们先创建一个辅助类。这个类的所有成员皆为私有类型,因为它不被普通用户所使用。为了只为智能指针使用,还需要把智能指针类声明为辅助类的友元。这个辅助类含有两个数据成员:计数count与基础对象指针。也即辅助类用以封装使用计数与基础对象指针。
辅助类:就是 把 int count; 和 Point *p; 封装成一个类,用于管理指针和计数)
class U_Ptr
{
private:
friend class SmartPtr;
U_Ptr(Point *ptr) :p(ptr), count(1) { }
~U_Ptr() { delete p; }
int count;
Point *p;
};
4.3.为基础对象类实现智能指针类
引用计数是实现智能指针的一种通用方法。智能指针将一个计数器与类指向的对象相关联,引用计数跟踪共有多少个类对象共享同一指针。它的具体做法如下:
当创建类的新对象时,初始化指针,并将引用计数设置为1
当对象作为另一个对象的副本时,复制构造函数复制副本指针,并增加与指针相应的引用计数(加1)
使用赋值操作符对一个对象进行赋值时,处理复杂一点:先使左操作数的指针的引用计数减1(为何减1:因为指针已经指向别的地方),如果减1后引用计数为0,则释放指针所指对象内存。然后增加右操作数所指对象的引用计数(为何增加:因为此时做操作数指向对象即右操作数指向对象)。
析构函数:调用析构函数时,析构函数先使引用计数减1,如果减至0则delete对象。
做好前面的准备后,我们可以来为基础对象类Point书写一个智能指针类了。根据引用计数实现关键点,我们可以写出我们的智能指针类如下:
class SmartPtr
{
public:
SmartPtr(Point *ptr) : rp(new U_Ptr(ptr)) { }
SmartPtr(const SmartPtr &sp) : rp(sp.rp)
{
++rp->count;
}
SmartPtr &operator=(const SmartPtr &rhs)
{
++rhs.rp->count;
if (--rp->count == 0)
delete rp;
rp = rhs.rp;
return *this;
}
~SmartPtr()
{
if (--rp->count == 0)
delete rp;
else
cout << "还有" << rp->count << "个指针指向基础对象" << endl;
}
private:
U_Ptr *rp;
};
为什么U_Ptr *rp,而不是 static Uptr rp 或者
参考:
// 此处有个重点的知识点
在没有封装成U_ptr类时,一般这样定义
int* m_pCount;
Point *p;
为什么不用static int m_pCount、int m_pCount:
1、若使用 static int m_pCount,则在一个new资源块(实例)下可以实现计数功能
若出现多个new资源块(实例)时,将无法实现多个资源块儿的正确释放,因为static属于类共享而不是资源块儿(对象)绑定。(多实例共享static)
2、若使用int m_pCount则与对象个数绑定,无法实现一个资源块儿下的多个指针对象m_pCount相同
则无法实现--m_pCount功能计数
3、为当前案例方式*m_pCount ,可以实现多个new资源块儿时,同步new m_pCount;
即可实现资源块儿与独立m_pCount资源的绑定,方便管理计数,此情况下,在拷贝的时候,将多个拷贝
关系的变量直接使用m_pCount地址传递,达到m_pCount资源在同一个资源块儿下多个对象共享的机制
int* m_pCount; // 资源计数器,为0时可以被释放,避免重复释放问题
连接:https://zhuanlan.zhihu.com/p/161629362
4.4.智能指针类的使用与测试
至此,我们的智能指针类就完成了,我们可以来看看如何使用
int main()
{
//定义一个基础对象类指针
Point *pa = new Point(10, 20);
//定义三个智能指针类对象,对象都指向基础类对象pa
//使用花括号控制三个指针指针的生命期,观察计数的变化
{
SmartPtr sptr1(pa); //此时计数count=1
{
SmartPtr sptr2(sptr1); //调用复制构造函数,此时计数为count=2
{
SmartPtr sptr3 = sptr1; //调用赋值操作符,此时计数为conut=3
}
//此时count=2
}
//此时count=1;
}
//此时count=0;pa对象被delete掉
cout << pa->getX () << endl;
system("pause");
return 0;
}
来看看运行结果咯:
如期,在离开大括号后,共享基础对象的指针从3->2->1->0变换,最后计数为0时,pa对象被delete,此时使用getX()已经获取不到原来的值。有兴趣一起交流学习c/c++的小伙伴可以加群:941636044,里面有大神会给予解答,也会有许多的资源可以供大家学习分享,欢迎大家前来一起学习进步!
3、改进
智能指针类的改进一
虽然我们的SmartPtr类称为智能指针,但它目前并不能像真正的指针那样有->、*等操作符,为了使它看起来更像一个指针,我们来为它重载这些操作符。代码如下所示:
{
public:
SmartPtr(Point *ptr) :rp(new U_Ptr(ptr)) { }
SmartPtr(const SmartPtr &sp) :rp(sp.rp) { ++rp->count; }
SmartPtr& operator=(const SmartPtr& rhs) {
++rhs.rp->count;
if (--rp->count == 0)
delete rp;
rp = rhs.rp;
return *this;
}
~SmartPtr() {
if (--rp->count == 0)
delete rp;
else
cout << "还有" << rp->count << "个指针指向基础对象" << endl;
}
Point & operator *() //重载*操作符
{
return *(rp->p);
}
Point* operator ->() //重载->操作符
{
return rp->p;
}
private:
U_Ptr *rp;
};
然后我们可以像指针般使用智能指针类
Point *pa = new Point(10, 20);
SmartPtr sptr1(pa);
//像指针般使用
cout<<sptr1->getX();
智能指针改进二
目前这个智能指针智能用于管理Point类的基础对象,如果此时定义了个矩阵的基础对象类,那不是还得重新写一个属于矩阵类的智能指针类吗?但是矩阵类的智能指针类设计思想和Point类一样啊,就不能借用吗?答案当然是能,那就是使用模板技术。为了使我们的智能指针适用于更多的基础对象类,我们有必要把智能指针类通过模板来实现。这里贴上上面的智能指针类的模板版:
//模板类作为友元时要先有声明
template <typename T>
class SmartPtr;
template <typename T>
class U_Ptr //辅助类
{
private :
//该类成员访问权限全部为private,因为不想让用户直接使用该类
friend class SmartPtr<T>;
//定义智能指针类为友元,因为智能指针类需要直接操纵辅助类
//构造函数的参数为基础对象的指针
U_Ptr(T *ptr) : p(ptr), count(1) { }
//析构函数
~U_Ptr()
{
delete p;
}
//引用计数
int count;
//基础对象指针
T *p;
};
template <typename T>
class SmartPtr //智能指针类
{
public :
SmartPtr(T *ptr) : rp(new U_Ptr<T>(ptr)) { } //构造函数
SmartPtr(const SmartPtr<T> &sp) : rp(sp.rp)
{
++rp->count;
}
//复制构造函数
SmartPtr &operator=(const SmartPtr<T> &rhs)
{
//重载赋值操作符
++rhs.rp->count;
//首先将右操作数引用计数加1,
if (--rp->count == 0) //然后将引用计数减1,可以应对自赋值
delete rp;
rp = rhs.rp;
return *this;
}
T &operator *() //重载*操作符
{
return *(rp->p);
}
T *operator ->() //重载->操作符
{
return rp->p;
}
~SmartPtr()
{
//析构函数
if (--rp->count == 0) //当引用计数减为0时,删除辅助类对象指针,从而删除基础对象
delete rp;
else
cout << "还有" << rp->count << "个指针指向基础对象" << endl;
}
private :
U_Ptr<T> *rp;
//辅助类对象指针
};
好啦,现在我们能够使用这个智能指针类对象来共享其他类型的基础对象啦,比如int:
int main()
{
int *i = new int(2);
{
SmartPtr<int> ptr1(i);
{
SmartPtr<int> ptr2(ptr1);
{
SmartPtr<int> ptr3 = ptr2;
cout << *ptr1 << endl;
*ptr1 = 20;
cout << *ptr2 << endl;
}
}
}
system("pause");
return 0;
}
运行结果如期所愿,SmartPtr类管理起int类型来了:
原文:链接:https://www.jianshu.com/p/e32037c905ac
值得一看的文章:https://blog.csdn.net/zhangruijerry/article/details/100927531
为什么多线程读写 shared_ptr 要加锁?
http://www.cppblog.com/Solstice/archive/2016/04/01/197597.html
因为 shared_ptr 有两个数据成员,读写操作不能原子化。