C++编程经验(9):智能指针 -- 裸指针管得了的我要管,裸指针管不了的我更要管!
@[toc]
智能指针介绍
智能指针是存储指向动态分配(堆)对象指针的类。除了能够在适当的时间自动删除指向的对象外,他们的工作机制很像C++的内置指针。智能指针在面对异常的时候格外有用,因为他们能够确保正确的销毁动态分配的对象。他们也可以用于跟踪被多用户共享的动态分配对象。
事实上,智能指针能够做的还有很多事情,例如处理线程安全,提供写时复制,确保协议,并且提供远程交互服务。有能够为这些ESP (Extremely Smart Pointers)创建一般智能指针的方法,但是并没有涵盖进来。
智能指针的大部分使用是用于生存期控制,阶段控制。它们使用operator->和operator*来生成原始指针,这样智能指针看上去就像一个普通指针。
手写智能指针
先来个例子对着看,不然对着空气我也尴尬。
#include<iostream>
using namespace std;
template<class T>
class smart_ptr {
public:
smart_ptr(T* ptr = nullptr){
mptr = ptr;
}
~smart_ptr() {
delete mptr;
}
private:
T* mptr;
};
int main() {
/*
int* p = new int;
delete p;
本来是这样写的吧
*/
smart_ptr<int> sp(new int); //现在这样写就好了
return 0;
}
这个栈上构造的对象一出作用域就会自动被析构掉,指针也就释放了。
既然我们现在要以这个类对象作为新的指针对象,那么就有这么一句很经典的话可以套进来了:“裸指针管得了的我要管,裸指针管不了的我更要管!”
所以这个类应该被丰富一下。
template<class T>
class smart_ptr {
public:
smart_ptr(T* ptr = nullptr){
mptr = ptr;
}
~smart_ptr() {
delete mptr;
}
T& operator*() {
return *(this->mptr);
}
T* operator->() {
return this->mptr;
}
private:
T* mptr;
};
int main() {
smart_ptr<int> sp(new int); //现在这样写就好了
*sp = 20;
cout << *sp << endl;
return 0;
}
不带引用计数的智能指针
上面这个代码就是不带引用计数的,那么它暴露出什么问题来了呢?
int main() {
smart_ptr<int> sp(new int); //现在这样写就好了
smart_ptr<int> ps(sp);
return 0;
}
这就崩溃了。为什么呢?因为在出作用域的时候,ps先析构了,把资源释放了;而轮到sp要析构的时候,就没有资源可以析构了。
析构之后置空?有用吗?并没有。
~smart_ptr() {
delete mptr;
mptr = nullptr;
}
问题在于这是个浅拷贝。
那去写个深拷贝?有用的。把资源拷贝到另一块空间,析构的时候,你走你的,我走我的。但是,这不就违背了我们最原始的初衷了吗?裸指针能做的,智能指针都要能做,那裸指针可以这样直接的复制后管的还是一块资源,智能指针就不行了?
那,怎么办?
升级手写的智能指针
带引用计数的智能指针:shared_ptr 和 weak_ptr,可以使多个智能指针管理同一个资源,实现方式:给每一个对象资源匹配一个引用计数。
#include<iostream>
using namespace std;
template<class TT>
class Refcnt {
//对资源进行引用计数的类
public:
Refcnt(TT* ptr = nullptr) {
mptr = ptr;
if (mptr) {
mcount = 1;
}
}
void addRef() {
mcount++;
}
int delRef() {
return --mcount;
}
private:
TT* mptr;
int mcount;
};
template<class T>
class smart_ptr {
public:
smart_ptr(T* ptr = nullptr){
mptr = ptr;
mRefCnt = new Refcnt<T>(mptr);
}
~smart_ptr() {
if (0 == mRefCnt->delRef()) {
delete mptr;
mptr = nullptr;
}
}
T& operator*() {
return *(this->mptr);
}
T* operator->() {
return this->mptr;
}
smart_ptr(const smart_ptr<T>& ptr) {
mptr = ptr.mptr;
mRefCnt = ptr.mRefCnt;
if (mptr != nullptr) {
mRefCnt->addRef();
}
}
smart_ptr<T>& operator=(smart_ptr<T> &ptr) {
if (this == &ptr) {
return *this;
}
//要走之前,要先判断一下自己原先的资源有没有人接管
if (0 == mRefCnt->delRef()) {
delete mptr;
}
mptr = ptr.mptr;
mRefCnt = ptr->mRefCnt;
mRefCnt->addRef();
return *this;
}
private:
T* mptr; //指向资源
Refcnt<T>* mRefCnt; //指向引用计数
};
int main() {
smart_ptr<int> sp(new int); //现在这样写就好了
smart_ptr<int> ps(sp);
return 0;
}
智能指针的循环引用问题
强智能指针
使用引用计数的指针有:shared_ptr 和 weak_ptr,一个称为强智能指针,一个称为若智能指针。
强智能指针可以改变资源的引用计数,弱智能指针不会。
我们前面写的那个就是简化版的强智能指针。
但是呢,强智能指针有个很严重的问题,叫做循环引用,或者说“交叉引用”,这么说会不会比较明显点。
#include<iostream>
#include<memory>
using namespace std;
class B;
class A {
public:
A() {
cout << "A" << endl;
}
~A() {
cout << "~A" << endl;
}
shared_ptr<B> _ptrb;
};
class B {
public:
B() {
cout << "B" << endl;
}
~B() {
cout << "~B" << endl;
}
shared_ptr<A> _ptra;
};
int main() {
shared_ptr<A> pa(new A());
shared_ptr<B> pb(new B());
cout << pa.use_count() << endl;
cout << pb.use_count() << endl;
//到这之前都很正常
//pa->_ptrb = pb;
//pb->_ptra = pa;
return 0;
}
A
B
1
1
~B
~A
那我现在把那两行放出来运行呢?
int main() {
shared_ptr<A> pa(new A());
shared_ptr<B> pb(new B());
//到这之前都很正常
pa->_ptrb = pb;
pb->_ptra = pa;
cout << pa.use_count() << endl;
cout << pb.use_count() << endl;
return 0;
}
A
B
2
2
智能指针没有被正确的析构掉,那还能叫智能指针吗?
弱智能指针
从上面的实验我们得出一个结论:在使用对象的时候,使用强智能指针;在引用对象的时候,使用弱智能指针。
怎么得出的呢?总不能说强智能指针不好用了就用弱的吧,主要是弱智能指针不改变计数,但是其实就相当于是一个观察者,对对象其实没有权限的。
改一下上面的代码,把类中的强智能指针改成弱的。再运行就正常。
弱智能指针升级为强智能指针
接下来是不是就要问这个问题嘞?
类B里面去调用类A里面的一个方法,看看行不行。
void testB() {
_ptra->testA();
}
很显然,是不行的。因为弱智能指针只会观察强指针。
那要用的时候怎么办呢?升级:
void testB() {
shared_ptr<A> ps = _ptra.lock(); //就是内个锁
if (ps != nullptr) { //看一下资源是否还在
ps->testA();
}
}
多线程访问共享对象
在muduo库中多次使用了强弱智能指针
看一下普通指针在线程中的表现:
void handler(A* q) {
std::this_thread::sleep_for(std::chrono::seconds(2));
q->testA();
}
int main() {
A* p = new A();
thread t1(handler, p);
delete p;
t1.join();
return 0;
}
这里问题就出来了,我先定义了一个线程,然后让线程休眠两秒,不过在主线程之中将这个指针给回收了,但是依然是在子线程中调用了 testA 的方法!!!
A
~A
A中的方法
再拿弱智能指针对比一下:
void handler(weak_ptr<A> q) {
//在使用智能指针的时候,要侦测指针是否存活
shared_ptr<A> sp = q.lock();
if (sp != nullptr) {
sp->testA();
}
else {
cout << "A对象已被析构" << endl;
}
}
int main() {
//加个作用域,让智能指针出作用域就析构掉
{
shared_ptr<A> p(new A());
thread t1(handler, weak_ptr<A>(p));
std::this_thread::sleep_for(std::chrono::seconds(2)); //可以试试这行屏蔽了再运行看看
t1.detach();
}
}
- 点赞
- 收藏
- 关注作者
评论(0)