前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >C++编程经验(9):智能指针 -- 裸指针管得了的我要管,裸指针管不了的我更要管!

C++编程经验(9):智能指针 -- 裸指针管得了的我要管,裸指针管不了的我更要管!

作者头像
看、未来
发布2021-10-09 15:30:15
5640
发布2021-10-09 15:30:15
举报
请添加图片描述
请添加图片描述

文章目录

智能指针介绍

智能指针是存储指向动态分配(堆)对象指针的类。除了能够在适当的时间自动删除指向的对象外,他们的工作机制很像C++的内置指针。智能指针在面对异常的时候格外有用,因为他们能够确保正确的销毁动态分配的对象。他们也可以用于跟踪被多用户共享的动态分配对象。 事实上,智能指针能够做的还有很多事情,例如处理线程安全,提供写时复制,确保协议,并且提供远程交互服务。有能够为这些ESP (Extremely Smart Pointers)创建一般智能指针的方法,但是并没有涵盖进来。 智能指针的大部分使用是用于生存期控制,阶段控制。它们使用operator->和operator*来生成原始指针,这样智能指针看上去就像一个普通指针。


手写智能指针

先来个例子对着看,不然对着空气我也尴尬。

代码语言:javascript
复制
#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;
}

这个栈上构造的对象一出作用域就会自动被析构掉,指针也就释放了。

既然我们现在要以这个类对象作为新的指针对象,那么就有这么一句很经典的话可以套进来了:“裸指针管得了的我要管,裸指针管不了的我更要管!”

所以这个类应该被丰富一下。

代码语言:javascript
复制
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;
}

不带引用计数的智能指针

上面这个代码就是不带引用计数的,那么它暴露出什么问题来了呢?

代码语言:javascript
复制
int main() {

	smart_ptr<int> sp(new int);		//现在这样写就好了
	smart_ptr<int> ps(sp);

	return 0;
}

这就崩溃了。为什么呢?因为在出作用域的时候,ps先析构了,把资源释放了;而轮到sp要析构的时候,就没有资源可以析构了。

析构之后置空?有用吗?并没有。

代码语言:javascript
复制
	~smart_ptr() {
		delete mptr;
		mptr = nullptr;
	}

问题在于这是个浅拷贝。

那去写个深拷贝?有用的。把资源拷贝到另一块空间,析构的时候,你走你的,我走我的。但是,这不就违背了我们最原始的初衷了吗?裸指针能做的,智能指针都要能做,那裸指针可以这样直接的复制后管的还是一块资源,智能指针就不行了?

那,怎么办?


升级手写的智能指针

带引用计数的智能指针:shared_ptr 和 weak_ptr,可以使多个智能指针管理同一个资源,实现方式:给每一个对象资源匹配一个引用计数。

代码语言:javascript
复制
#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,一个称为强智能指针,一个称为若智能指针。 强智能指针可以改变资源的引用计数,弱智能指针不会。

我们前面写的那个就是简化版的强智能指针。

但是呢,强智能指针有个很严重的问题,叫做循环引用,或者说“交叉引用”,这么说会不会比较明显点。

代码语言:javascript
复制
#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;
}
代码语言:javascript
复制
A
B
1
1
~B
~A

那我现在把那两行放出来运行呢?

代码语言:javascript
复制
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;
}
代码语言:javascript
复制
A
B
2
2

智能指针没有被正确的析构掉,那还能叫智能指针吗?


弱智能指针

从上面的实验我们得出一个结论:在使用对象的时候,使用强智能指针;在引用对象的时候,使用弱智能指针。

怎么得出的呢?总不能说强智能指针不好用了就用弱的吧,主要是弱智能指针不改变计数,但是其实就相当于是一个观察者,对对象其实没有权限的。

改一下上面的代码,把类中的强智能指针改成弱的。再运行就正常。


弱智能指针升级为强智能指针

接下来是不是就要问这个问题嘞?

类B里面去调用类A里面的一个方法,看看行不行。

代码语言:javascript
复制
	void testB() {
		_ptra->testA();
	}

很显然,是不行的。因为弱智能指针只会观察强指针。

那要用的时候怎么办呢?升级:

代码语言:javascript
复制
	void testB() {
		shared_ptr<A> ps = _ptra.lock();	//就是内个锁
		if (ps != nullptr) {	//看一下资源是否还在
			ps->testA();
		}
	}

多线程访问共享对象

在muduo库中多次使用了强弱智能指针

看一下普通指针在线程中的表现:

代码语言:javascript
复制
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 的方法!!!

代码语言:javascript
复制
A
~A
A中的方法

再拿弱智能指针对比一下:

代码语言:javascript
复制
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();
	}
}

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2021-08-24 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 文章目录
  • 智能指针介绍
  • 手写智能指针
    • 不带引用计数的智能指针
    • 升级手写的智能指针
    • 智能指针的循环引用问题
      • 强智能指针
        • 弱智能指针
          • 弱智能指针升级为强智能指针
          • 多线程访问共享对象
          领券
          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档