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

举报
看,未来 发表于 2021/09/06 21:35:24 2021/09/06
【摘要】 @[toc] 智能指针介绍智能指针是存储指向动态分配(堆)对象指针的类。除了能够在适当的时间自动删除指向的对象外,他们的工作机制很像C++的内置指针。智能指针在面对异常的时候格外有用,因为他们能够确保正确的销毁动态分配的对象。他们也可以用于跟踪被多用户共享的动态分配对象。事实上,智能指针能够做的还有很多事情,例如处理线程安全,提供写时复制,确保协议,并且提供远程交互服务。有能够为这些ESP ...

请添加图片描述

@[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();
	}
}

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。