C++:智能指针

为什么需要智能指针

下面我们先分析一下下面这段程序有没有什么 内存方面 的问题?提示一下:注意分析 MergeSort函数中的问题。
int div()
{
 int a, b;
 cin >> a >> b;
 if (b == 0)
 throw invalid_argument("除0错误");
 return a / b;
}
void Func()
{
// 1、如果p1这里new 抛异常会如何?
// 2、如果p2这里new 抛异常会如何?
// 3、如果div调用这里又会抛异常会如何?
 int* p1 = new int;
 int* p2 = new int;
 cout << div() << endl;
 delete p1;
delete p2;
}
int main()
{
 try
 {
 Func();
 }
 catch (exception& e)
 {
 cout << e.what() << endl;
 }
 return 0;
}

内存泄漏

什么是内存泄漏,内存泄漏的危害
什么是内存泄漏:内存泄漏指因为疏忽或错误造成程序未能释放已经不再使用的内存的情况。内
存泄漏并不是指内存在物理上的消失,而是应用程序分配某段内存后,因为设计错误,失去了对 该段内存的控制,因而造成了内存的浪费。
内存泄漏的危害:长期运行的程序出现内存泄漏,影响很大,如操作系统、后台服务等等,出现 内存泄漏会导致响应越来越慢,最终卡死。

内存泄漏分类(了解)

C/C++ 程序中一般我们关心两种方面的内存泄漏:
  • 堆内存泄漏(Heap leak) 堆内存指的是程序执行中依据须要分配通过malloc / calloc / realloc / new等从堆中分配的一块内存,用完后必须通过调用相应的 free或者delete 删掉。假设程序的设计错误导致这部分内存没有被释放,那么以后这部分空间将无法再被使用,就会产生Heap Leak
  • 系统资源泄漏  指程序使用系统分配的资源,比方套接字、文件描述符、管道等没有使用对应的函数释放掉,导致系统资源的浪费,严重可导致系统效能减少,系统执行不稳定。

如何避免内存泄漏

1. 工程前期良好的设计规范,养成良好的编码规范,申请的内存空间记着匹配的去释放。 ps
这个理想状态。但是如果碰上异常时,就算注意释放了,还是可能会出问题。需要下一条智
能指针来管理才有保证。
2. 采用 RAII 思想或者智能指针来管理资源。
3. 有些公司内部规范使用内部实现的私有内存管理库。这套库自带内存泄漏检测的功能选项。
4. 出问题了使用内存泄漏工具检测。 ps :不过很多工具都不够靠谱,或者收费昂贵。
总结一下:
内存泄漏非常常见,解决方案分为两种:
1 、事前预防型。如智能指针等。 2 、事后查错型。如泄漏检测工具

智能指针的使用及原理

RAII

智能指针头文件:<memory>

// 使用RAII思想设计的SmartPtr类
template<class T>
class SmartPtr {
public:
    SmartPtr(T* ptr = nullptr)
       : _ptr(ptr)
   {}
    ~SmartPtr()
   {
        if(_ptr)
            delete _ptr;
   }
    
private:
    T* _ptr;
};
int div()
{
 int a, b;
 cin >> a >> b;
 if (b == 0)
 throw invalid_argument("除0错误");
 return a / b;
}
void Func()
{
 ShardPtr<int> sp1(new int);
 ShardPtr<int> sp2(new int);
 cout << div() << endl;
}

int main()
{
    try {
          Func();
        }
    catch(const exception& e)
   {
        cout<<e.what()<<endl;
   }
 return 0;
}

智能指针的原理

上述的 SmartPtr 还不能将其称为智能指针,因为它还不具有指针的行为。指针可以解引用,也可 以通过-> 去访问所指空间中的内容,因此: AutoPtr 模板类中还得需要将 * -> 重载下,才可让其
像指针一样去使用
template<class T>
class SmartPtr {
public:
SmartPtr(T* ptr = nullptr)
     : _ptr(ptr)
 {}
~SmartPtr()
 {
     if(_ptr)
         delete _ptr;
 }
T& operator*() {return *_ptr;}
T* operator->() {return _ptr;}
private:
T* _ptr;
};
struct Date
{
    int _year;
 int _month;
 int _day;
};
int main()
{
SmartPtr<int> sp1(new int);
*sp1 = 10
cout<<*sp1<<endl;
SmartPtr<int> sparray(new Date);
// 需要注意的是这里应该是sparray.operator->()->_year = 2018;
// 本来应该是sparray->->_year这里语法上为了可读性,省略了一个->
sparray->_year = 2018;
sparray->_month = 1;
sparray->_day = 1;
}
总结一下智能指针的原理:
1. RAII 特性
2. 重载 operator* opertaor-> ,具有像指针一样的行为。

auto_ptr

C++98 版本的库中就提供了 auto_ptr 的智能指针。下面演示的 auto_ptr 的使用及问题。
auto_ptr 的实现原理:管理权转移的思想,下面简化模拟实现了一份 bit::auto_ptr 来了解它的原
// C++98 管理权转移 auto_ptr
namespace bit
{
 template<class T>
 class auto_ptr
 {
 public:
 auto_ptr(T* ptr)
 :_ptr(ptr)
 {}
 auto_ptr(auto_ptr<T>& sp)
 :_ptr(sp._ptr)
 {
 // 管理权转移
 sp._ptr = nullptr;
 }
 auto_ptr<T>& operator=(auto_ptr<T>& ap)
 {
 // 检测是否为自己给自己赋值
 if (this != &ap)
 {
 // 释放当前对象中资源
 if (_ptr)
 delete _ptr;
 // 转移ap中资源到当前对象中
 _ptr = ap._ptr;
 ap._ptr = NULL;
 }
 return *this;
 }
 ~auto_ptr()
 {
 if (_ptr)
 {
 cout << "delete:" << _ptr << endl;
 delete _ptr;
 }
 }
 // 像指针一样使用
 T& operator*()
 {
 return *_ptr;
 }
 T* operator->()
 {
 return _ptr;
 }
 private:
 T* _ptr;
 };
}
// 结论:auto_ptr是一个失败设计,很多公司明确要求不能使用auto_ptr
//int main()
//{
// std::auto_ptr<int> sp1(new int);
// std::auto_ptr<int> sp2(sp1); // 管理权转移
//
// // sp1悬空
// *sp2 = 10;
// cout << *sp2 << endl;
// cout << *sp1 << endl;
// return 0;
//}

unique_ptr

C++11 中开始提供更靠谱的 unique_ptr
unique_ptr 的实现原理:简单粗暴的防拷贝,下面简化模拟实现了一份 UniquePtr 来了解它的原
// C++11库才更新智能指针实现
// C++11出来之前,boost搞除了更好用的scoped_ptr/shared_ptr/weak_ptr
// C++11将boost库中智能指针精华部分吸收了过来
// C++11->unique_ptr/shared_ptr/weak_ptr
// unique_ptr/scoped_ptr
// 原理:简单粗暴 -- 防拷贝
namespace bit
{
 template<class T>
 class unique_ptr
 {
 public:
 unique_ptr(T* ptr)
 :_ptr(ptr)
 {}
 ~unique_ptr()
{
 if (_ptr)
 {
 cout << "delete:" << _ptr << endl;
 delete _ptr;
 }
 }
 // 像指针一样使用
 T& operator*()
 {
 return *_ptr;
 }
 T* operator->()
 {
 return _ptr;
 }
 unique_ptr(const unique_ptr<T>& sp) = delete;
 unique_ptr<T>& operator=(const unique_ptr<T>& sp) = delete;
 private:
 T* _ptr;
 };
}

//int main()
//{
// /*bit::unique_ptr<int> sp1(new int);
// bit::unique_ptr<int> sp2(sp1);*/
//
// std::unique_ptr<int> sp1(new int);
// //std::unique_ptr<int> sp2(sp1);
//
// return 0;
//}

shared_ptr

C++11 中开始提供更靠谱的并且支持拷贝的 shared_ptr
shared_ptr 的原理:是通过引用计数的方式来实现多个 shared_ptr 对象之间共享资源
1. shared_ptr 在其内部, 给每个资源都维护了着一份计数,用来记录该份资源被几个对象共
2. 对象被销毁时 ( 也就是析构函数调用 ) ,就说明自己不使用该资源了,对象的引用计数减一
3. 如果引用计数是 0 ,就说明自己是最后一个使用该资源的对象, 必须释放该资源
4. 如果不是 0 ,就说明除了自己还有其他对象在使用该份资源, 不能释放该资源 ,否则其他对象就成野指针了。
C++11 中的 shared_ptr 提供了两种初始化方式,包括直接使用 new 和使用 make_shared。make_shared 在内存分配上更高效,仅需一次内存分配,同时包含管理对象和数据对象。当强引用计数为0但弱引用计数不为0时,内存无法释放,直至弱引用计数也清零才释放。
// 引用计数支持多个拷贝管理同一个资源,最后一个析构对象释放资源
namespace bit
{
 template<class T>
 class shared_ptr
 {
 public:
 shared_ptr(T* ptr = nullptr)
 :_ptr(ptr)
 , _pRefCount(new int(1))
 , _pmtx(new mutex)
 {}
 shared_ptr(const shared_ptr<T>& sp)
 :_ptr(sp._ptr)
 , _pRefCount(sp._pRefCount)
 , _pmtx(sp._pmtx)
 {
 AddRef();
 }
 void Release()
 {
 _pmtx->lock();
 bool flag = false;
 if (--(*_pRefCount) == 0 && _ptr)
 {
 cout << "delete:" << _ptr << endl;
 delete _ptr;
 delete _pRefCount;
 flag = true;
 }
 _pmtx->unlock();
 if (flag == true)
 {
 delete _pmtx;
 }
 }
 void AddRef()
 {
 _pmtx->lock();
 ++(*_pRefCount);
 _pmtx->unlock();
 }
 shared_ptr<T>& operator=(const shared_ptr<T>& sp)
 {
 //if (this != &sp)
 if (_ptr != sp._ptr)
 {
 Release();
_ptr = sp._ptr;
 _pRefCount = sp._pRefCount;
 _pmtx = sp._pmtx;
 AddRef();
 }
 return *this;
 }
 int use_count()
 {
 return *_pRefCount;
 }
 ~shared_ptr()
 {
 Release();
 }
 // 像指针一样使用
 T& operator*()
 {
 return *_ptr;
 }
 T* operator->()
 {
 return _ptr;
 }
 T* get() const
 {
 return _ptr;
 }
 private:
 T* _ptr;
 int* _pRefCount;
 mutex* _pmtx;
 };
}

或者

#include<atomic>

namespace bit
{
	template<class T>
	class shared_ptr
	{
	public:
		shared_ptr(T* ptr)
			:_ptr(ptr)
			,_pcount(new atomic<int>(1))
		{}

		// sp2(sp1)
		shared_ptr(const shared_ptr<T>& sp)
			:_ptr(sp._ptr)
			,_pcount(sp._pcount)
		{
			(*_pcount)++;
		}

		// sp1 = sp3;
		shared_ptr<T>& operator=(const shared_ptr<T>& sp)
		{
			//if (this != &sp)
			if(_ptr != sp._ptr)
			{
				this->release();

				_ptr = sp._ptr;
				_pcount = sp._pcount;

				++(*_pcount);
			}

			return *this;
		}

		void release()
		{
			if (--(*_pcount) == 0)
			{
				// 最后一个管理的对象,释放资源
				delete _ptr;
				delete _pcount;
			}
		}

		~shared_ptr()
		{
			release();
		}

		int use_count()
		{
			return *_pcount;
		}

		T& operator*()
		{
			return *_ptr;
		}

		T* operator->()
		{
			return _ptr;
		}
	private:
		T* _ptr;
		atomic<int>* _pcount;
	};
}
shared_ptr智能指针是线程安全的吗?

智能指针对象本身拷贝析构是线程安全的

底层引用计数加减是线程安全的

指向的资源访问不是线程安全的

std::shared_ptr 的线程安全问题
1. 智能指针对象中引用计数是多个智能指针对象共享的,两个线程中智能指针的引用计数同时 ++或 -- ,这个操作不是原子的,引用计数原来是 1 ++ 了两次,可能还是 2, 这样引用计数就错乱了。会导致资源未释放或者程序崩溃的问题。所以只能指针中引用计数++ -- 是需要加锁的,也就是说引用计数的操作是线程安全的。
2. 智能指针管理的对象存放在堆上,两个线程中同时去访问,会导致线程安全问题。

weak_ptr

std::weak_ptr 是一种智能指针,通常不单独使用,只能和 shared_ptr 类型指针搭配使用,可以视为 shared_ptr 指针的一种辅助工具。借助 weak_ptr 类型指针可以获取 shared_ptr 指针的一些状态信息,比如有多少指向相同的 shared_ptr 指针、通过expired()判断shared_ptr 指针指向的堆内存是否已经被释放等等,还可以解决shared_ptr 循环引用的问题。

weak_ptr可以从一个shared_ptr或者另一个weak_ptr对象构造,获得资源的观测权。但weak_ptr没有共享资源,它的构造不会引起指针引用计数的增加。使用weak_ptr的成员函数use_count()可以观测资源的引用计数,另一个成员函数expired()的功能等价于use_count()==0,成立表示被观测的资源(也就是shared_ptr的管理的资源)已经不复存在。

namespace bit
{
template<class T>
 class weak_ptr
 {
 public:
 weak_ptr()
 :_ptr(nullptr)
 {}
 weak_ptr(const shared_ptr<T>& sp)
 :_ptr(sp.get())
 {}
 weak_ptr<T>& operator=(const shared_ptr<T>& sp)
 {
 _ptr = sp.get();
 return *this;
 }
 T& operator*()
 {
 return *_ptr;
 }
 T* operator->()
 {
 return _ptr;
 }
 private:
 T* _ptr;
 };
}

std::shared_ptr的循环引用

struct ListNode
{
 int _data;
shared_ptr<ListNode> _prev;
 shared_ptr<ListNode> _next;
 ~ListNode(){ cout << "~ListNode()" << endl; }
};
int main()
{
 shared_ptr<ListNode> node1(new ListNode);
 shared_ptr<ListNode> node2(new ListNode);
 cout << node1.use_count() << endl;
 cout << node2.use_count() << endl;
 node1->_next = node2;
 node2->_prev = node1;
 cout << node1.use_count() << endl;
 cout << node2.use_count() << endl;
 return 0;
}

// 解决方案:在引用计数的场景下,把节点中的_prev和_next改成weak_ptr就可以了
// 原理就是,node1->_next = node2;和node2->_prev = node1;时weak_ptr的_next和_prev不会增加node1和node2的引用计数。
struct ListNode
{
 int _data;
 weak_ptr<ListNode> _prev;
 weak_ptr<ListNode> _next;
 ~ListNode(){ cout << "~ListNode()" << endl; }
};
int main()
{
 shared_ptr<ListNode> node1(new ListNode);
 shared_ptr<ListNode> node2(new ListNode);
 cout << node1.use_count() << endl;
 cout << node2.use_count() << endl;
 node1->_next = node2;
 node2->_prev = node1;
 cout << node1.use_count() << endl;
 cout << node2.use_count() << endl;
 return 0;
}

定制删除器

如果不是 new 出来的对象如何通过智能指针管理呢?其实 shared_ptr 设计了一个删除器来解决这
个问题
class A
{
public:
	A(int a1 = 0, int a2 = 0)
		:_a1(a1)
		, _a2(a2)
	{}

	~A()
	{
		cout << "~A()" << endl;
	}

private:
	int _a1 = 1;
	int _a2 = 1;
};

template<class T>
struct FreeFunc {
	void operator()(T* ptr)
	{
		cout << "free:" << ptr << endl;
		free(ptr);
	}
};

// 定制删除器
int main()
{
	//std::shared_ptr<A[]> sp1(new A[10]);
	bit::shared_ptr<A> sp1(new A[10], [](A* ptr) {delete[] ptr; });
	bit::shared_ptr<int> sp2((int*)malloc(4), FreeFunc<int>());
	bit::shared_ptr<FILE> sp3(fopen("test.txt", "w"), [](FILE* ptr) {fclose(ptr); });

	bit::shared_ptr<A> sp4(new A);

	return 0;
}

智能指针析构时默认用delete释放内存,释放多个对象或变量时则会出错

例如std::shared_ptr<A[ ]> sp1(new A[10]);,要加[ ]才能调用delete[ ]。

delete和delete[ ]都能释放单个内存变量或单个对象空间,也能释放malloc申请的单个空间

多空间只能用delete[ ]。

定制器可以自定义释放空间的方式,实现释放多样化。

将删除器的包装器加入shared_ptr的模板实现调用删除器的功能

#include<atomic>
#include<functional>

namespace bit
{
	template<class T>
	class shared_ptr
	{
	public:
		shared_ptr(T* ptr)
			:_ptr(ptr)
			, _pcount(new atomic<int>(1))
		{}

		template<class D>
		shared_ptr(T* ptr, D del)
			: _ptr(ptr)
			, _pcount(new atomic<int>(1))
			, _del(del)
		{}

		// sp2(sp1)
		shared_ptr(const shared_ptr<T>& sp)
			:_ptr(sp._ptr)
			, _pcount(sp._pcount)
		{
			(*_pcount)++;
		}

		// sp1 = sp3;
		shared_ptr<T>& operator=(const shared_ptr<T>& sp)
		{
			//if (this != &sp)
			if (_ptr != sp._ptr)
			{
				this->release();

				_ptr = sp._ptr;
				_pcount = sp._pcount;

				++(*_pcount);
			}

			return *this;
		}

		void release()
		{
			if (--(*_pcount) == 0)
			{
				// 最后一个管理的对象,释放资源
				//delete _ptr;
				_del(_ptr);

				delete _pcount;
			}
		}

		~shared_ptr()
		{
			release();
		}

		int use_count()
		{
			return *_pcount;
		}

		T& operator*()
		{
			return *_ptr;
		}

		T* operator->()
		{
			return _ptr;
		}
	private:
		T* _ptr;
		atomic<int>* _pcount;

		function<void(T*)> _del = [](T* ptr) {delete ptr; };
	};
}

C++11boost中智能指针的关系

1. C++ 98 中产生了第一个智能指针 auto_ptr.
2. C++ boost 给出了更实用的 scoped_ptr shared_ptr weak_ptr.
3. C++ TR1 ,引入了 shared_ptr 等。不过注意的是 TR1 并不是标准版。
4. C++ 11 ,引入了 unique_ptr shared_ptr weak_ptr 。需要注意的是 unique_ptr 对应 boost的scoped_ptr 。并且这些智能指针的实现原理是参考 boost 中的实现的。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

你好,赵志伟

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值