C++11智能指针与内存泄漏

前言

这也是我最近刷面经看到的问题。对于智能指针,其实理解起来还是很容易的,这里我面向初学者,详细介绍一下智能指针的东西,然后咱们一起尝试写一个智能指针,最后讲一下内存泄漏的检测方法,开整。

内存泄漏

我之前做开发的同事经常碰到的问题就是内存泄漏,那么什么是内存泄漏呢?在计算机科学中,内存泄漏指由于疏忽或错误造成程序未能释放已经不再使用的内存。内存泄漏并非指内存在物理上的消失,而是应用程序分配某段内存后,由于设计错误,导致在释放该段内存之前就失去了对该段内存的控制,从而造成了内存的浪费。
内存泄漏的危害真的非常大,它能使软件无可用内存而崩溃。
那么如何检测内存泄漏呢?首先,内存泄漏的表现,有的很严重(在任务管理器中发现内存占用率一直在攀升),当然有的就表现不是那么明显。有什么好的检测方法么?
1.VS下可以直接用crt检测

#define CRTDBG_MAP_ALLOC  //放在程序最前
#include <iostream>
#include <stdlib.h>    
#include <crtdbg.h> 
using namespace std;

int main()
{
	int *a = new int [10];
	int *p = new int[1000];
	_CrtDumpMemoryLeaks();  //放在程序最后
	system("pause");
	return 0;
}

然后程序运行的即使窗口会显示第几次内存分配时忘记释放:在这里插入图片描述
发现原来是第65次和第66次,所以咱们直接加上断点:

#define CRTDBG_MAP_ALLOC
#include <iostream>
#include <stdlib.h>    
#include <crtdbg.h> 
using namespace std;

int main()
{
	_CrtSetBreakAlloc(65);
	//_CrtSetBreakAlloc(66);  //分别取消注释即可触发所有断点。
	int *a = new int [10];
	int *p = new int[1000];
	_CrtDumpMemoryLeaks();
	system("pause");
	return 0;
}

OK等下就能触发断点,此断点应该是某个new或者malloc操作。
2.Linux下用valgrind
这可是神器,咱们就不在这篇文章中展开了,我单独写个文章解释一下这个东西。
好了刚刚写完了就回来了。继续。

C++11智能指针

智能指针本来有四类,分别是shared_ptr unique_ptr weak_ptr和auto_ptr,可是c++17取消了最后一个auto_ptr,所以咱们就最好能别用它。
其实简言之,智能指针就两个,分别是可共享的shared_ptr和独占的unique_ptr,至于weak_ptr,那玩意儿纯粹是用来解决shared_ptr的问题才出现的。因此咱们重点介绍前二者,冲冲冲!

unique_ptr

这是个独占式的指针对象,在任何时间、资源只能被一个指针占有,当unique_ptr离开作用域,指针所包含的内容会被释放。很显然,unique_ptr类中具有移动构造函数和重载移动赋值运算符,但是没有拷贝构造函数和重载拷贝赋值运算符,对吧,不然爆炸了呀。
我就不用c++14中提到的make_unique来创建unique_ptr了,直接就普普通通的来了。
先看看unique_ptr的定义:

// non-specialized 
template <class T, class D = default_delete<T>> class unique_ptr;
// array specialization   
template <class T, class D> class unique_ptr<T[],D>;

原来人家给我们设置了默认的删除器,当然咱们自己也可以自己写一个删除器,比如来个简单的:

void myDeletor(int* val)
{
	delete val;
	std::cout << "balabala" << std::endl;
}
unique_ptr<int, decltype(myDeletor)*> uptrA(new int(10), myDeletor);

接着是简单的一些操作

unique_ptr<int> uptrA( new int );
unique_ptr<int[ ]> uptrB( new int[5] );
//获取管理的指针,建议不要使用
auto ptr = uptrA.get();
//release操作是将uptr管理的内存放弃管理,并返回这个之前被管理的指针,同时将自己管理的内容置为nullptr
uptrA.release();//还是建议不要这么用,不太合适
unique_ptr<int> uptrC(uptrA.release());	//这样就很棒,一去一回
swap(uptrA, uptrB);//交换两个智能指针管理的对象
uptrA.swap(uptrB);

shared_ptr

shared_ptr在管理资源时同时会创建一个引用计数,每个shared_ptr对象关联一个共享的引用计数,当某shared_ptr超过作用范围时,会检查引用计数值,若自减操作后refCount==0,则释放管理的资源。
shared_ptr的自定义删除器和unique_ptr稍微有点不同,即它不需要在类模板中指明删除器类型。
创建方法也很简单:

int main() {
    shared_ptr<string> pStr = make_shared<string>(10, '1');
    cout << *pStr << endl;  //  1111111111

    int *p = new int(0);
    shared_ptr<int> pInt(p);
    cout << *pInt << endl;  // 0
}

另外允许在创建shared_ptr时传入一个shared_ptr,这样他俩都指向一块内存,且refCount++。
如果用=赋值的话,等号左侧的shared_ptr的refCount会做自减操作,同时会指向右侧的内存以及refCount++。
OK咱们具体的就不多说了,等一下咱们会手写一版咱们自己的shared_ptr。

weak_ptr

环形引用这个问题大家肯定很容易就能想到,比如A类里有个private成员,是个shared_ptr,同时还有个B类,B类里也有个private成员,是个shared_ptr。结果巧了,A类的private指向B类成员,同时B类这个成员的private又指向A类。。。。。。
那完了,我想释放哪个都做不到了,因为refCount肯定大于等于2!!!僵硬了。
weak_ptr干的事情,就是去打破这个环,让自己作为环的弱势一方。其实这事儿也有点难受,因为啥呢,是不是有环产生这件事情得咱们程序员自己去判断,因此还是很有风险的。
weak_ptr用的也不多,我就只简单了解到此。

智能指针的陷阱

智能指针确实很不错,咱们如果按照人家的规矩办事,方便啊。可是要不按照规矩来,可就很僵硬了:
1.千万不要用相同的指针,去初始化多个智能指针!!!
2.建议少用get操作!!!
3.如果智能指针管理的资源并不是new得到的,请务必给他提个删除器!!!

自己动手写一个shared_ptr

#pragma once 

#ifndef _SMART_POINTER_H_
#define _SMART_POINTER_H_

template <typename T>
class SmartPtr {
private:
	T* refValue;
	int* refCount;
private:
	void accCount()
	{
		if (refCount != nullptr)
			++* refCount;
	}
	void decCount()
	{
		if (refCount && -- * refCount == 0)
		{
			delete refValue;
			delete refCount;
			refValue = refCount = nullptr;
		}
	}
public:
	//常规构造函数
	SmartPtr() :refValue(nullptr), refCount(nullptr) {}
	SmartPtr(T* value) :refValue(value) {
		refCount = new int( (value == nullptr) ? 0 : 1 );
	}
	//常规析构函数
	~SmartPtr()
	{
		decCount();
	}
	//拷贝构造函数
	SmartPtr(const SmartPtr& ptr)
	{
		refValue = ptr.refValue;
		refCount = ptr.refCount;
		accCount();
	}
	//重载赋值操作符
	SmartPtr& operator=(const SmartPtr& ptr)
	{
		decCount();
		refValue = ptr.refValue;
		refCount = ptr.refCount;
		accCount();
		return *this;
	}
	//重载解引用操作符
	T operator*()
	{
		return *refValue;
	}
	//获取引用计数
	int getRefCount()
	{
		return *refCount;
	}
};

#endif

OK还是很简单的,注意一下拷贝构造函数和重载赋值运算符即可。

猜你喜欢

转载自blog.csdn.net/weixin_44039270/article/details/106615883