带引用计数的智能指针:shared_ptr和weak_ptr
带引用计数的好处: 多个智能指针可以管理同一个资源。
C++11
boost库的智能指针拿到标准库里面
带引用计数:给每一个对象的资源,匹配一个引用计数
当1个智能指针引用这个资源的时候,这个资源相应的引用计数就加1,当这个智能指针出作用域,不再使用这个资源的时候,这个资源的引用计数就减1。
当引用计数减1不为0的时候,这个智能指针不使用这个资源了,但是还有其他智能指针在使用这个资源,这个智能指针不能析构这个资源,只能直接走人。
当引用计数减1为0的时候,说明当前智能指针是最后使用这个资源的智能指针,所以它要负责这个资源的释放。
模拟实现shared_ptr
引用计数类的实现:
然后我们给之前定义的智能指针,添加这个成员
智能指针构造的时候给资源建立引用计数对象
析构的时候也要- -判断
拷贝构造函数实现如下:
赋值函数如下:
现在这样做就可以了
多个智能指针管理同一个资源了
template<typename T>
class RefCnt
{
public:
RefCnt(T* ptr = nullptr)
:mptr(ptr)
{
if (mptr != nullptr)
{
mcount = 1;
}
}
void addRef() { mcount++; }
int defRef() { return --mcount; }
void show() { cout << mcount << endl; }
private:
T* mptr;
atomic_int mcount;
};
template<typename T>
class CSmartPtr
{
public:
CSmartPtr(T* p = nullptr)
:ptr(p)
{
mpRefCnt = new RefCnt<T>(ptr);
}
~CSmartPtr()
{
if (0 == mpRefCnt->defRef())
{
delete ptr;
ptr = nullptr;
}
}
CSmartPtr(const CSmartPtr<T>& src)
:ptr(src.ptr), mpRefCnt(src.mpRefCnt)
{
if (ptr != nullptr)
{
mpRefCnt->addRef();
}
}
CSmartPtr<T>& operator=(const CSmartPtr<T>& src)
{
if (this == &src)
{
return *this;
}
if (0 == mpRefCnt->defRef())
{
delete ptr;
}
ptr = src.ptr;
mpRefCnt = src.mpRefCnt;
mpRefCnt->addRef();
return *this;
}
T& operator*()
{
return *ptr;
}
T* operator->()
{
return ptr;
}
void Count() { return mpRefCnt->show(); }
private:
T* ptr;
RefCnt<T>* mpRefCnt;
};
int main()
{
CSmartPtr<int>p(new int(50));
CSmartPtr<int>q(p);
cout <<*p<< endl;
cout << *q << endl;
p.Count();
cout << p.operator->() << endl;
}
库里实现的shared_ptr和weak_ptr是线程安全的!
可以直接使用在多线程环境下
库里是用atomic_int定义的
shared_ptr的交叉引用问题
弱智能指针观察强智能指针,强智能指针观察资源(内存)
强智能指针循环引用(交叉引用)是什么问题?什么结果?怎么解决?
运行一下
但是如果我们这么调用呢?
我们运行一下
出作用域都是从2减到1,根本达不到析构的条件。
造成对象new出来的资源无法释放,严重的资源泄漏问题。
如何解决这个问题呢?
运行成功
弱智能指针只是观察对象是否活着,不改变引用计数
假如说,现在A里面有一个好的方法
B中的func函数调用不了A的方法
因为弱智能指针只是1个观察者,只会观察资源,不能去使用资源。
弱智能指针根本没有提供*运算符重载和->运算符重载。
但是我们可以这么用
在多线程中,弱智能指针观察的资源有可能被释放有可能没有被释放,主要就是根据引用计数是否为0,有可能,弱智能指针需要使用对象,需要从一个观察者提升为强智能指针,在提升的过程中有可能提升失败,资源已经释放了,有可能提升成功,资源还没释放。
运行成功
class B;
class A
{
public:
A() { cout << "A()构造" << endl; }
~A() { cout << "~A()析构" << endl; }
weak_ptr<B>ptr2;
void testA() { cout << "非常好的方法" << endl; }
};
class B
{
public:
B() { cout << "B()构造" << endl; }
~B() { cout << "~B()析构" << endl; }
void func()
{
shared_ptr<A>ps = ptr1.lock();
if (ps != nullptr)
{
ps->testA();
}
cout << ps.use_count() << endl;//智能指针ps提升成功,引用技术加1到2
//智能指针ps出函数作用域自动析构,引用计数从2减到1
}
weak_ptr<A>ptr1;
};
int main()
{
shared_ptr<A>ptra(new A());
shared_ptr<B>ptrb(new B());
ptra->ptr2 = ptrb;
ptrb->ptr1 = ptra;
cout << ptra.use_count() << endl;//1
cout << ptra.use_count() << endl;//1
ptrb->func();
cout << ptra.use_count() << endl;//1
cout << ptra.use_count() << endl;//1
ptrb->func();
}
多线程访问共享对象的线程安全问题
我们看下面例子
运行如下
A对象就是main线程和子线程共享的对象。
如果我们现在对代码进行修改
这样合适么?析构了才去访问这个对象的方法,显然,非常不合理!
析构之后,很可能访问原先的资源已经没有了
给对象添加引用计数,使用强弱智能指针!
newA的资源由主线程控制,子线程只引用它,这个对象到时候有的话就访问它,没有的话就不访问它的方法。
智能指针自定义删除器
并不是说所有资源的释放都是delete指针 进行释放的。
比如说,用智能指针来托管数组的资源,delete就得加个中括号[]了,
如果用智能指针管理的是文件资源,或者是其他资源,释放的方式不是delete。
这2个智能指针都可以提供自定义删除器。
先构造的后析构,后构造的先析构
但是这样写很麻烦,要写很多模板类。
我们使用lambda表达式!!!