IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> C++知识库 -> 体验智能指针的强大 -> 正文阅读

[C++知识库]体验智能指针的强大

基础知识

裸指针存在的问题

  1. 忘记释放资源导致资源泄漏(发生内存泄漏问题)
  2. 同一资源释放多次,导致释放野指针,程序崩溃
  3. 明明代码的后面写了资源释放的代码,但是由于程序的逻辑满足条件,从中间return掉了,导致释放资源的代码未被执行到
  4. 代码运行过程中发生异常,随着异常栈展开,导致释放资源的代码未被执行到

智能指针

保证做到资源的自动释放,用户可以不关注资源的释放,因为智能指针会帮助你完全管理资源的释放,它会保证无论程序逻辑怎么跑,正常执行或者产生异常,资源在到期的情况下,一定会进行释放。

C++11库里面,提供了带引用计数的智能指针和不带引用计数的智能指针,本文主要介绍原理和应用场景。

自己实现智能指针

利用栈上的对象出作用域自动析构的特征做到自动释放,把资源释放的代码全部放在析构函数中执行,就达到了所谓的智能指针。

#include<iostream>
using namespace std;

template<typename T>
class CSmartPtr
{
public:
	CSmartPtr(T* ptr = nullptr)
		:mptr(ptr){}
	T& operator*()
	{
		return *mptr;
	}
	T* operator->()
	{
		return mptr;
	}
	~CSmartPtr()
	{
		delete mptr;
	}
private:
	T* mptr;

};
int main()
{
	CSmartPtr<int> ptr1(new int);
	*ptr1 = 20;
	class Test
	{
	public:
		void test()
		{
			cout << "call Test::test()" << endl;
		}
	};
	CSmartPtr<Test> ptr2(new Test());
	ptr2->test();
	return 0;
}

上面的智能指针主要用到了两点:

  1. 智能指针体现了把裸指针进行一次面向对象的封装,在构造函数中初始化资源地址,在析构函数中释放资源
  2. 利用栈上的对象做出作用域自动析构这个特点,在智能指针的析构函数中保证释放资源

智能指针不能直接用于堆上,如CSmartPtr *p=new CSmartPtr(new int)是错误的。

不带引用计数的智能指针

int main()
{
	CSmartPtr<int> p1(new int);
	CSmartPtr<int> p2(p1);
	return 0;
}

上面代码运行直接崩溃,因为默认的拷贝构造函数做的是浅拷贝,两个智能指针都持有一个new int资源,到ptr1析构的时候,ptr2先析构释放资源了,到ptr1析构的时候,就成了delete野指针了,造成程序崩溃。所以智能指针就要解决浅拷贝问题和保证多指针指向同一资源的时候只能释放一次

  1. auto_ptr(C++库里面)

不推荐使用auto_ptr。从C++源码可知只有最后一个auto_ptr智能指针持有资源,原来的auto_ptr都赋成nullptr了。

int main()
{
	auto_ptr<int> ptr1(new int);
	auto_ptr<int> ptr2(ptr1);

	*ptr2 = 20;
	cout << *ptr1 << endl;
	//ptr1已经赋值为nullptr,再次访问很危险
	return 0;
}

能否在容器中使用auto_ptr?

vector<auto_ptr> vec1;vec2(vec1);

不能,因为容器使用过程中,可能会做容器的拷贝构造和赋值,就会引起容器里面每一个元素的拷贝构造和赋值,如果说给容器里面全部放的auto_ptr,用vec1构造一个新的vec2容器出来,那么vec1底层的裸指针会被全部置成nullptr,所以auto_ptr不要做过于复杂的操作,会转移对象的所有权。

总结:auto_ptr智能指针不带引用计数,解决浅拷贝问题,是直接把前面的auto_ptr都置为nullptr,只让最后一个auto_ptr持有资源。

  1. scoped_ptr(C++11新标准,仅仅只能使用单个对象,使用的少)
	scoped_ptr(const scoped_ptr<T>&)=delete;
	scoped_ptr<T>&operator=(const scoped_ptr<T>&)=delete;

scoped_ptr智能指针私有化了拷贝构造函数和赋值函数,从根本上杜绝了智能指针浅拷贝,所以scoped_ptr也是不能用在容器当中的,如果容器互相进行拷贝或者赋值,就会引起scoped_ptr对象的拷贝构造和赋值,这是不允许的,代码会提示错误。

auto_ptr和scoped_ptr的区别:

auto_ptr会任意转移资源的所有权,scoped_ptr不会转移资源的所有权,因为拷贝构造和赋值都被禁止了

  1. unique_ptr(C++11新标准,推荐使用)
		unique_ptr(const unique_ptr<T>&)=delete;
		unique_ptr<T>&operator=(const unique_ptr<T>&)=delete;

		unique_ptr(unique_ptr<T> &&src)
		unique_ptr<T>&operator=(unique_ptr<T>&&src)
		template<typename T>
		unique_ptr<T> getSmartPtr()
		{
			unique_ptr<T> ptr(new T())
			return ptr;
		}
		unique_ptr<T> ptr=getSmartPtr<int>()
		ptr1=getSmartPtr<int>()
int main()
{
	unique_ptr<int> p1(new int);
	unique_ptr<int> p2(std::move(p1));
	return 0;
}

unique_ptr有一点和scoped_ptr一样,就是去掉了拷贝构造函数和赋值函数,但是unique_ptr提供了带右值引用参数的拷贝构造和赋值。

实现带引用计数的智能指针

带引用计数的智能指针shared_ptr和weak_ptr

带引用计数:多个智能指针可以管理同一个资源

带引用计数:给每一个资源对象,匹配一个引用计数;

智能指针 =》资源的时候 =》引用计数+1

智能指针 =》不使用资源的时候 =》引用计数-1 =》!=0 0资源释放了

#include<iostream>
#include<memory>
using namespace std;

//对资源进行引用计数的类
template<typename T>
class RefCnt
{
public:
	RefCnt(T* ptr = nullptr)
		:mptr(ptr)
	{
		if (mptr != nullptr)
			mcount = 1;
	}
	void addRef(){ mcount++; }//增加资源的引用计数
	int delRef(){ return --mcount; }
private:
	T* mptr;
	int mcount;//atomic_int CAS
};

template<typename T>
class CSmartPtr
{
public:
	CSmartPtr(T* ptr = nullptr)
		:mptr(ptr)
	{
		mpRefCnt = new RefCnt<T>(mptr);
	}
	CSmartPtr(const CSmartPtr<T>& src)
		:mptr(src.mptr), mpRefCnt(src.mpRefCnt)
	{
		if (mptr != nullptr)
			mpRefCnt->addRef();
	}
	CSmartPtr<T>& operator=(const CSmartPtr<T>& src)
	{
		if (this == &src)
			return *this;
		if (mpRefCnt->delRef() == 0)
		{
			delete mptr;
		}
		mptr = src.mptr;
		mpRefCnt = src.mpRefCnt;
		mpRefCnt->addRef();
		return *this;
	}
	~CSmartPtr()
	{
		if (mpRefCnt->delRef() == 0)
		{
			delete mptr;
			mptr = nullptr;
		}
	}
	T& operator*()
	{
		return *mptr;
	}
	T* operator->()
	{
		return mptr;
	}
	
private:
	T* mptr;
	RefCnt<T>* mpRefCnt;//指向该资源引用计数对象的指针
};
int main()
{
	CSmartPtr<int> ptr1(new int);
	CSmartPtr<int> ptr2(ptr1);
	CSmartPtr<int> ptr3;
	ptr3 = ptr2;
	*ptr1 = 20;
	cout << *ptr2 << " " << *ptr3 << endl;
	return 0;
}

shared_ptr的交叉引用问题

shared_ptr:强智能指针 可以改变资源的引用计数

weak_ptr:弱智能指针 不会改变资源的引用计数

weak_ptr观察shared_ptr,shared_ptr观察资源(内存),没有提供operator* operator->

强智能指针循环引用(交叉引用)是什么问题?什么结果?怎么解决?

造成new出来的资源无法释放!资源泄露问题!
定义对象的时候用强智能指针,引用对象的时候用弱智能指针

#include<iostream>
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());

	pa->_ptrb = pb;
	pb->_ptra = pa;

	cout << pa.use_count() << endl;
	cout << pb.use_count() << endl;

	return 0;
}

强智能指针的循环引用问题导致资源无法释放
在这里插入图片描述

定义对象的时候用强智能指针,引用对象的时候用弱智能指针

#include<iostream>
using namespace std;
class B;
class A
{
public:
	A() { cout << "A()" << endl; }
	~A() { cout << "~A()" << endl; }
	weak_ptr<B> _ptrb;
};
class B
{
public:
	B() { cout << "B()" << endl; }
	~B() { cout << "~B()" << endl; }
	weak_ptr<A> _ptra;
};
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;
}

weak_ptr没有提供operator*和 operator->,可以将weak_ptr提升为shared_ptr,然后使用operator*和operator->方法

#include<iostream>
using namespace std;

class B;
class A
{
public:
	A() { cout << "A()" << endl; }
	~A() { cout << "~A()" << endl; }
	void testA() { cout << "非常好用的方法!" << endl; }
	weak_ptr<B> _ptrb;
};
class B
{
public:
	B() { cout << "B()" << endl; }
	~B() { cout << "~B()" << endl; }
	void func()
	{
		shared_ptr<A> ps = _ptra.lock();//提升方法
		if (ps != nullptr)
		{
			ps->testA();
		}
	}
	weak_ptr<A> _ptra;
};
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;
	pb->fun();
	return 0;
}

多线程访问共享对象的线程安全问题

#include<iostream>
#include <thread>
using namespace std;

/*
C++著名的开源网络库muduo库
多线程访问共享对象的线程安全问题
*/
class A
{
public:
	A() { cout << "A()" << endl; }
	~A() { cout << "~A()" << endl; }
	void testA() { cout << "非常好用的方法!" << endl; }
};
//子线程
void handler01(A* q)
{
	q->testA();
}
//main线程
int main()
{
	A* p = new A();
	thread t1(handler01, p);
	std::this_thread::sleep_for(std::chrono::seconds(2));
	delete p;
	//阻塞等待子进程结束
	t1.join();
	return 0;
}

在这里插入图片描述

//子线程
void handler01(A* q)
{
	std::this_thread::sleep_for(std::chrono::seconds(2));
	//q在访问A对象的时候,需要侦测A对象是否存活
	q->testA();
}
//main线程
int main()
{
	A* p = new A();
	thread t1(handler01, p);
	delete p;
	return 0;
}

A对象已经被析构了就不能再调用了!!!
在这里插入图片描述

//子线程
void handler01(weak_ptr<A> pw)
{
	std::this_thread::sleep_for(std::chrono::seconds(2));
	//q在访问A对象的时候,需要侦测A对象是否存活
	shared_ptr<A> sp = pw.lock();
	if (sp != nullptr)
	{
		sp->testA();
	}
	else
	{
		cout << "A对象已经析构,不能再访问!" << endl;
	}
}
//main线程
int main()
{
	{
		shared_ptr<A> p(new A());
		thread t1(handler01, weak_ptr<A>(p));
		//分离线程
		t1.detach();
		//std::this_thread::sleep_for(std::chrono::seconds(2));
	}
	std::this_thread::sleep_for(std::chrono::seconds(20));
	return 0;
}

在这里插入图片描述

自定义删除器

智能指针的删除器:deletor

智能指针:能够保证资源的绝对释放

#include<iostream>
using namespace std;
#include<functional>
/*
~unique_ptr是一个函数对象的调用 deletor(ptr)
template<typename T>
class Deletor
{
public:
	void operator()(T *ptr)
	{
		delete ptr;
	}
};
*/
template<typename T>
class MyDeletor
{
public:
	void operator()(T* ptr)const
	{
		cout << "call MyDeletor.operator()" << endl;
		delete[]ptr;
	}
};
template<typename T>
class MyFileDeletor
{
public:
	void operator()(T* ptr)const
	{
		cout << "call MyFileDeletor.operator()" << endl;
		fclose(ptr);
	}
};
int main()
{
	//unique_ptr<int, MyDeletor<int>> ptr1(new int[100]);
	//unique_ptr<FILE, MyFileDeletor<FILE>> ptr2(fopen("data.txt","w"));
	
	//lambda表达式=>函数对象 function
	unique_ptr<int, function<void(int*)>> ptr1(new int[100],
		[](int* p)->void {
			cout << "call MyDeletor.operator()" << endl;
			delete[]p;
		}
	);
		unique_ptr<FILE, function<void(FILE*)>> ptr2(fopen("data.txt", "w"),
			[](FILE* p)->void {
				cout << "call MyFileDeletor.operator()" << endl;
				fclose(p);
			}
		);
	return 0;
}

在这里插入图片描述

  C++知识库 最新文章
【C++】友元、嵌套类、异常、RTTI、类型转换
通讯录的思路与实现(C语言)
C++PrimerPlus 第七章 函数-C++的编程模块(
Problem C: 算法9-9~9-12:平衡二叉树的基本
MSVC C++ UTF-8编程
C++进阶 多态原理
简单string类c++实现
我的年度总结
【C语言】以深厚地基筑伟岸高楼-基础篇(六
c语言常见错误合集
上一篇文章      下一篇文章      查看所有文章
加:2021-09-14 13:08:38  更:2021-09-14 13:10:13 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/23 21:53:41-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码