目录

1. 智能指针的使用场景分析

2. RAII和智能指针的设计思路

3. C++标准库智能指针的使用

4. 智能指针的原理

4.1 auto_ptr 的实现:(了解)

4.2 unique_ptr的实现:

4.3 shared_ptr的实现:

4.4 shared_ptr 赋值的实现:

4.5 完整的代码,仅供参考:

5. shared_ptr 和 weak_ptr

5.1 shared_ptr循环引用问题

5.2 weak_ptr

6.shared_ptr的线程安全问题

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

8. 内存泄露

8.1 什么是内存泄漏,内存泄漏的危害

8.2 如何检测内存泄漏(了解)

8.3 如何避免内存泄漏


1. 智能指针的使用场景分析

下⾯程序中我们可以看到,new了以后,我们也delete了,但是因为抛异常导,后⾯的delete没有得到执行,所以就内存泄漏了,所以我们需要new以后捕获异常,捕获到异常后delete内存,再把异常抛出,但是因为new本⾝也可能抛异常,连续的两个new和下面的Divide都可能会抛异常,让我们处理起来很麻烦。智能指针放到这样的场景里面就让问题简单多了。

以下代码再使用上一篇文章的方法的时候:捕获异常,释放内存,再重新抛出异常的方式,此时就不太起作用了。

double Divide(int a, int b)
{
	// 当b == 0时抛出异常
	if (b == 0)
	{
		throw "Divide by zero condition!";
	}
	else
	{
		return (double)a / (double)b;
	}
}
void Func()
{
	//这里可以看到如果发生除0错误抛出异常,另外下面的array和array2没有得到释放。
	//所以这里捕获异常后并不处理异常,异常还是交给外面处理,这里捕获了再重新抛出去。
	//但是如果array2 new的时候抛异常呢,就还需要套一层捕获释放逻辑,这里更好解决方案
	//是智能指针,否则代码太戳了
	int* array1 = new int[10];
	int* array2 = new int[10]; // 抛异常呢
	try
	{
		int len, time;
		cin >> len >> time;
		cout << Divide(len, time) << endl;
	}
	catch (...)
	{
		cout << "delete []" << array1 << endl;
		cout << "delete []" << array2 << endl;
		delete[] array1;
		delete[] array2;
		throw; // 异常重新抛出,捕获到什么抛出什么
	}
	// ...
	cout << "delete []" << array1 << endl;
	delete[] array1;
	cout << "delete []" << array2 << endl;
	delete[] array2;
}
int main()
{
	try
	{
		Func();
	}
	catch (const char* errmsg)
	{
		cout << errmsg << endl;
	}
	catch (const exception& e)
	{
		cout << e.what() << endl;
	}
	catch (...)
	{
		cout << "未知异常" << endl;
	}
	return 0;
}

是因为这段程序还有一个缺陷:new本身也会抛异常

  •     int* array1 = new int[10]; 

    //抛异常,直接就到外层的main或者是处理异常的那部分中去了,无资源释放,因为都抛异常了

  •     cout << Divide(len, time) << endl;

    //Divide抛异常也是没问题的

  •     int* array2 = new int[10];

    // 抛异常,还要释放前面两个,一个是第一个new,一个是Divide,所以再套一层try catch,

去捕获第二个new抛异常,在套几个new呢?还要再套几层try catch吗?—— 所以引出智能指针的这样一个东西来解决此问题。

2. RAII和智能指针的设计思路

  • RAII是Resource Acquisition Is Initialization(资源请求立即初始化)的缩写,他是⼀种管理资源的类的设计思想,本质是⼀种利用对象生命周期来管理获取到的动态资源,避免资源泄漏,这⾥的资源可以是内存、文件指针、网络连接、互斥锁等等。RAII在获取资源时把资源委托给⼀个对象,接着控制对资源的访问资源在对象的生命周期内始终保持有效,最后在对象析构的时候释放资源,这样保障了资源的正常释放,避免资源泄漏问题。
  • 智能指针类除了满足RAII的设计思路,还要方便资源的访问,所以智能指针类还会想迭代器类⼀样,重载 operator*/operator->/operator[] 等运算符,方便访问资源。
#define _CRT_SECURE_NO_WARNINGS 1

#include<iostream>
using namespace std;

template<class T>
class SmartPtr
{
public:

	// RAII
	SmartPtr(T* ptr)
		:_ptr(ptr)
	{
	}

	~SmartPtr()
	{
		cout << "delete[] " << _ptr << endl;
		delete[] _ptr;
	}

	// 重载运算符,模拟指针的行为,方便访问资源
	T& operator*()
	{
		return *_ptr;
	}

	T* operator->()
	{
		return _ptr;
	}

	T& operator[](size_t i)
	{
		return _ptr[i];
	}

private:
	T* _ptr;
};

double Divide(int a, int b)
{
	// 当b == 0时抛出异常
	if (b == 0)
	{
		throw "Divide by zero condition!";
	}
	else
	{
		return (double)a / (double)b;
	}
}

void Func()
{
	// 这里使用RAII的智能指针类管理new出来的数组以后,程序简单多了
	SmartPtr<int> sp1 = new int[10];
	SmartPtr<int> sp2 = new int[10];
	for (size_t i = 0; i < 10; i++)
	{
		sp1[i] = sp2[i] = i;
	}
	int len, time;
	cin >> len >> time;
	cout << Divide(len, time) << endl;
}

int main()
{
	try
	{
		Func();
	}
	catch (const char* errmsg)
	{
		cout << errmsg << endl;
	}
	catch (const exception& e)
	{
		cout << e.what() << endl;
	}
	catch (...)
	{
		cout << "未知异常" << endl;
	}
	return 0;
}

出了作用域自动调用析构:

SmartPtr<int> sp1 = new int[10];  //抛异常,sp1,sp2未被实例化,直接跳到抛异常的地方,没有资源要被释放
SmartPtr<int> sp2 = new int[10];  //抛异常,sp2未被实例化,但是sp1被实例化,抛异常,直接跳到抛异常的地方去,但是栈展开的时候,栈帧还是正常结束的,sp1会正常调用它的析构

cout << Divide(len, time) << endl; //抛异常,前两个new是会正常析构的,因为资源都交给了智能指针,会自动释放,保存这个资源。本质就是构造的时候保存资源,出了作用域就会调用析构,析构就会释放这个资源

3. C++标准库智能指针的使用

  • 智能指针在C++98的时候就已经写出来了。C++标准库中的智能指针都在<memory>这个头文件下面,我们包含<memory>就可以是使⽤了,智能指针有好几种,除了weak_ptr他们都符合RAII和像指针⼀样访问的行为,原理上而言主要是解决智能指针拷贝时的思路不同。(真正的核心问题是拷贝的问题)
SmartPtr<int> sp3(sp1);  //用sp1去拷贝sp3,此时析构就会出事

因为我们没有写拷贝构造,默认就是浅拷贝,让sp1和sp3指向同一块资源,但是这里也不能深拷贝,因为这里和之前的容器不一样,之前的容器vector、list、map存储数据的空间就是属于容器本身的,拷贝这些容器就需要将这些数据和空间都重新拷贝一份,完成深拷贝,智能指针模拟的是指针的行为,sp1只是帮忙管理资源的,sp3(sp1)用一个指针拷贝另一个指针,希望两个指针共同管理同一块资源,共同管理的话就会发生析构多次的问题的出现。所以这一块:SmartPtr<int> sp3(sp1);  就是一个坑。所以智能指针就开启了它的发展历史:

  • auto_ptr是C++98时设计出来的智能指针,他的特点是拷贝时把被拷贝对象的资源的管理权转移给拷贝对象这是⼀个非常糟糕的设计,因为他会到被拷贝对象悬空,访问报错的问题,C++11设计出新的智能指针后,强烈建议不要使⽤auto_ptr。其它C++11出来之前很多公司也是明令禁止使⽤这个智能指针的。

(真正的问题:在解决拷贝的时候叫做自动指针,也就是自动释放,出了作用域自动释放,拷贝的设计非常糟糕,设计了一个管理权转移,非常糟糕)

struct Date
{
	int _year;
	int _month;
	int _day;

	Date(int year = 1,int month = 1,int day = 1)
		:_year(year)
		,_month(month)
		,_day(day)
	{ }

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

int main()
{
	auto_ptr<Date> ap1(new Date);
	//拷贝时,管理权限转移,被拷贝对象ap1悬空
	auto_ptr<Date> ap2(ap1);

	return 0;
}

运行结果:

以上代码析构是没有问题的,但是在将ap1拷贝给ap2时,会将ap1给置空,是有问题的:

不知道的人,还以为ap1依然存在,就去访问了,但是此时ap1已经不存在了:

int main()
{
    //……

	//语法上是支持的
	ap1->_day++;
	return 0;
}

此时就有大坑:

  • unique_ptr是C++11设计出来的智能指针,他的名字翻译出来是唯⼀指针,他的特点的不支持拷贝,只支持移动。如果不需要拷贝的场景就非常建议使用他。
int main()
{
	unique_ptr<Date> up1(new Date);
	//不支持拷贝
	//unique_ptr<Date> up2(up1);
	//支持移动,但是移动后up1也悬空(主观动作),所以使用移动要谨慎
	//unique_ptr<Date> up3(move(up1));
	return 0;
}

  • shared_ptr是C++11设计出来的智能指针,他的名字翻译出来是共享指针,他的特点是支持拷贝,也支持移动。如果需要拷贝的场景就需要使用他了。底层是用引用计数的方式实现的
int main()
{
	shared_ptr<Date> sp1(new Date);
	// 支持拷贝
	shared_ptr<Date> sp2(sp1);
	shared_ptr<Date> sp3(sp2);
	cout << sp1.use_count() << endl;
	sp1->_year++;
	cout << sp1->_year << endl;
	cout << sp2->_year << endl;
	cout << sp3->_year << endl;
	// 支持移动,但是移动后sp1也悬空,所以使用移动要谨慎
	shared_ptr<Date> sp4(move(sp1));
	return 0;
}

问题:

直接用 shared_ptr 就好了,为什么还要用unique_ptr呢?

shared_ptr 也是有代价的,管理引用计数也是有一定的消耗和成本的,尤其是在多线程的环境下面,引用计数还会涉及到加锁之类的问题来保证引用计数的线程安全,也是付出了一定的代价的。如果是不用支持拷贝的话,用unique_ptr更高效一些。

对以上三个智能指针使用的总结:

不需要拷贝用unique_ptr,需要拷贝用shared_ptr,无论什么场景都不要用auto_ptr

  • weak_ptr是C++11设计出来的智能指针,他的名字翻译出来是弱指针,他完全不同于上面的智能指针,他不支持RAII,也就意味着不能用它直接管理资源,weak_ptr的产生本质是要解决shared_ptr的⼀个循环引用导致内存泄漏的问题。具体细节下⾯我们再详细描述。
  • 智能指针析构时默认是进行delete释放资源,这也就意味着如果不是new出来的资源,交给智能指针管理,析构时就会崩溃。智能指针支持在构造时给⼀个删除器,所谓删除器本质就是⼀个可调用对象(可调用对象是什么都行:参数指针、lambda、包装器,对于shared_ptr是在构造的时候给,这个可调用对象中实现你想要的释放资源的方式比,当构造智能指针时,给了定制的删除器,在智能指针析构时就会调⽤删除器去释放资源。因为new[]经常使用,所以为了简洁⼀点,unique_ptr和shared_ptr都特化了⼀份[]的版本,使用时unique_ptr<Date[]> up1(new Date[5]);shared_ptr<Date[]> sp1(new Date[5]); 就可以管理new []的资源。
  • template <class T, class... Args> shared_ptr<T> make_shared
    (Args&&... args);

make_shared和make_pair有点类似,是一个可以变模板参数:

int main()
{
	//new一个日期类对象,给给shared_ptr。自己new
	shared_ptr<Date> sp1(new Date(2026, 4, 13));

	//构造日期类的参数直接传给make_shared,会返回一个shared_ptr。
	//参数传给它,帮你在内部new,new出来的东西给给给智能指针
	shared_ptr<Date> sp2 = make_shared<Date>(2026, 4, 13);

	//简写的方式,make_shared返回的是shared_ptr
	auto sp3 = make_shared<Date>(2026, 4, 13);
	shared_ptr<Date> sp4;
	return 0;
}

有人说,第二种写法:shared_ptr<Date> sp2 = make_shared<Date>(2026, 4, 13);更高效一点。例如下图:

按照我们的理解的话,上图中是分开的,new一份资源,再new一份引用计数,导致智能指针太多了的话,引用计数就是内存碎片,大量的去开辟小块内存,项目中大量的之智能指针,就有大量的引用计数,就是4个字节的小内存,就是大量内存碎片的问题,效率也有问题,大量的向系统申请小块内存,就会有效率问题,为了缓解这样的问题就有这样一些解决方案:

第一个就是允许在构造的时候传个内存池过去:(shared_ptr)

第二个就是使用make_shared,就是将构造Date的参数给给我,会将两个空间开到一起,开个日期类对象是12字节,引用计数是4字节,直接将两个合到一起,开16字节,头部存4字节的引用计数

  • shared_ptr 除了⽀持用指向资源的指针构造,还支持make_shared 用初始化资源对象的值直接构造。
  • shared_ptr 和 unique_ptr 都支持了operator bool的类型转换(operator bool可以对类型进行重载,跟普通的运算符不一样,普通的运算符都是运算符,这个是可以让shared_ptr 强转成bool类型),如果智能指针对象是⼀个空对象没有管理资源,则返回false,否则返回true,意味着我们可以直接把智能指针对象给if判断是否为空。

如果想强转成int,就是operator int。

上述的 explicit operator bool() const noexcept; 是没有返回值的,因为bool就是它的返回值。为什么不是operator+运算符呢?因为运算符被占用了。

std::shared_ptr::operator bool 判断 shared_ptr 有没有管理资源,不传参数,构造的 shared_ptr 调用它的默认构造,默认构造管理的是空指针。

int main()
{
	//new一个日期类对象,给给shared_ptr。自己new
	shared_ptr<Date> sp1(new Date(2026, 4, 13));

	//构造日期类的参数直接传给make_shared,会返回一个shared_ptr。
	//参数传给它,帮你在内部new,new出来的东西给给给智能指针
	shared_ptr<Date> sp2 = make_shared<Date>(2026, 4, 13);

	//简写的方式,make_shared返回的是shared_ptr
	auto sp3 = make_shared<Date>(2026, 4, 13);
	shared_ptr<Date> sp4;

	if(sp1.operator bool())
	//if (sp1)
		cout << "sp1 is not nullptr" << endl;

	if (!sp4)
		cout << "sp4 is nullptr" << endl;

	return 0;
}

int main()
{
	//报错,Date* 会生成一个临时对象,临时对象再去拷贝构造给给 sp5
	//构造+拷贝构造 直接优化成直接构造,老编译有可能不优化
	shared_ptr<Date> sp5 = new Date(2026, 4, 13);
	unique_ptr<Date> sp6 = new Date(2026, 4, 13);

	return 0;
}

基于这样的原因,不能这样写 (添加了explicit,就不允许隐式类型转换):

所以我们再写的时候也最好将explicit加上:

template<class T>
class shared_ptr
{
public:
	explicit shared_ptr(T* ptr = nullptr)
		:_ptr(ptr)
		,_pcount(new int(1))
	{}
    //……
}
  • shared_ptr 和 unique_ptr 都得构造函数都使⽤explicit 修饰,防止普通指针隐式类型转换成智能指针对象。

4. 智能指针的原理

 在C++98到C++11之间的时间中产生了一个特殊库:boost,不是C++11的标准库,boost是一个第三方的库,第三方库是由单独的源代码,下载下来根据使用方法来使用。C++11中的unique_ptr就是boost库中的scoped_ptr/scoped_array,C++11中的shared_ptr就是boost库中的shared_ptr/shared_array,C++11中的weak_ptr就是boost库中的weak_ptr,但是有些小细节改动了一下。同样右值引用的移动语义、bind、包装器也是boost中的,C++11吸收了。

4.1 auto_ptr 的实现:(了解)

// auto_ptr的实现

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;
};

4.2 unique_ptr的实现:

template<class T>
class unique_ptr
{
public:
	explicit 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;  //特点:不让拷贝
	unique_ptr(unique_ptr<T> && sp)
		:_ptr(sp._ptr)
	{
		sp._ptr = nullptr;
	}

	unique_ptr<T>& operator=(unique_ptr<T> && sp)
	{
		delete _ptr;
		_ptr = sp._ptr;
		sp._ptr = nullptr;
	}

private:
	T* _ptr;
};

4.3 shared_ptr的实现:

引用计数的实现不是那么容易实现的:

将_count变为静态成员:static int _count;确实是共享同一个,但是他是整个类的所有对象共享同一个,在以下的情况下就是不行的:

	ysy::shared_ptr<Date> sp1(new Date);
	ysy::shared_ptr<Date> sp2(sp1);

	ysy::shared_ptr<Date> sp3(new Date);

上面的情况是需要有两个引用计数的,sp1和sp2共用一个,sp3单独一个,但是当我们将_count变量定义为static int _count;时,sp1、sp2、sp3共用这同一个_count变量。

那应该如何定义这个引用计数呢?

各自里面弄一个指针:int* _pcount,有一个指针指向资源,资源在堆上去new一个出来,有资源就配一个计数。然后各自指向这个计数。所有的资源都是通过构造来进行管理,构造的时候说明就来了一份资源。

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

		//拷贝构造
		shared_ptr(const shared_ptr<T>& sp)
			:_ptr(sp._ptr)
			,_pcount(sp._pcount)
		{
			++(*_pcount);
		}

		~shared_ptr()
		{
			if (--(*_pcount) == 0)
			{
				delete _ptr;
				delete _pcount;
			}
		}

		//像指针一样使用
		T& operator*()
		{
			return *_ptr;
		}

		T* operator->()
		{
			return _ptr;
		}
	private:
		T* _ptr;
		//static int _count;  //静态成员共享同一个,但是不行
		int* _pcount;
	};
}

运行结果:

两次析构没问题。

4.4 shared_ptr 赋值的实现:

	//sp1 = sp3 (sp3为sp,sp1为this)
	//赋值考虑的问题最多
	shared_ptr<T>& operator=(const shared_ptr<T>& sp)
	{
		_ptr = sp._ptr;
		_pcount = sp._pcount;
		++(*_pcount);

		return *this;
	}

上述写的赋值的代码时最容易写错的,赋值的特点是两个已经存在的对象,直接让sp1指向sp3指向的资源的话,sp1之前的资源是不管了吗?如果sp1之前的资源只有sp1管理的话,那么这里就会有内存泄漏的问题,直接delete sp1之前的资源是不合理的。因为sp2也指向sp1原来指向的资源,直接delete sp1之前的资源的话,此时sp2指向的资源也被释放!!!sp2莫名其妙就指向野指针了!!!

	//sp1 = sp3 (sp3为sp,sp1为this)
	//赋值考虑的问题最多
	shared_ptr<T>& operator=(const shared_ptr<T>& sp)
	{
        delete _ptr;   //直接释放sp1之前的资源是不合理的
		_ptr = sp._ptr;
		_pcount = sp._pcount;
		++(*_pcount);

		return *this;
	}

正确的写法:

	template<class T>
	class shared_ptr
	{
	public:
        
        //……

		//sp1 = sp3 (sp3为sp,sp1为this)
		//赋值考虑的问题最多
		shared_ptr<T>& operator=(const shared_ptr<T>& sp)
		{
			if (--(*_pcount) == 0)
			{
				delete _ptr;
				delete _pcount;
			}

			_ptr = sp._ptr;
			_pcount = sp._pcount;
			++(*_pcount);

			return *this;
		}

		~shared_ptr()
		{
			if (--(*_pcount) == 0)
			{
				delete _ptr;
				delete _pcount;
			}
		}

		//像指针一样使用
		T& operator*()
		{
			return *_ptr;
		}

		T* operator->()
		{
			return _ptr;
		}
	private:
		T* _ptr;
		//static int _count;  //静态成员共享同一个,但是不行
		int* _pcount;
	};
}

int main()

{
	ysy::shared_ptr<Date> sp1(new Date);
	ysy::shared_ptr<Date> sp2(sp1);

	ysy::shared_ptr<Date> sp3(new Date);

	sp1 = sp3;

	return 0;
}

int main()

{
	ysy::shared_ptr<Date> sp1(new Date);
	ysy::shared_ptr<Date> sp2(sp1);

	ysy::shared_ptr<Date> sp3(new Date);

	sp1 = sp3;
	sp2 = sp3;

	return 0;
}

此时sp1、sp2、sp3都去管同一块资源了,sp2原来指向的资源没有智能指针管了,会被析构。资源的释放不一定是析构的时候释放,也有可能在赋值的时候释放资源,因为那块资源没有指针管理了,总得释放。

还有一点比较坑的就是防止自己给自己赋值的情况:

	
    template<class T>
	class shared_ptr
	{
	public:
		shared_ptr(T* ptr = nullptr)
			:_ptr(ptr)
			,_pcount(new int(1))
		{}
        //拷贝构造
		shared_ptr(const shared_ptr<T>& sp)
			:_ptr(sp._ptr)
			, _pcount(sp._pcount)
		{
			++(*_pcount);
		}

		void release()
		{
			if (--(*_pcount) == 0)
			{
				delete _ptr;
				delete _pcount;
			}
		}

		//sp1 = sp3 (sp3为sp,sp1为this)
		//赋值考虑的问题最多
		shared_ptr<T>& operator=(const shared_ptr<T>& sp)
		{
			if (_ptr != sp._ptr)
			{
				//避免自己给自己赋值
				release();
				_ptr = sp._ptr;
				_pcount = sp._pcount;
				++(*_pcount);

			}
			return *this;
		}

		~shared_ptr()
		{
			release();
		}

		//……
	};
}

int main()

{
	ysy::shared_ptr<Date> sp1(new Date);
	ysy::shared_ptr<Date> sp2(sp1);

	ysy::shared_ptr<Date> sp3(new Date);

	sp1 = sp1;
	sp1 = sp2;

	sp1 = sp3;
	sp2 = sp3;

	return 0;
}

new[ ]申请的必须要用delete[ ]去释放,不然就是坑。

在boost库中的解决方案是单独解决了一个shared_array的东西,shared_array和shared_ptr析构的不同,一个是delete,一个是delete[],shared_array重点实现重载operator[ ]。第一种方法就是定制删除器。

shared_ptr和unique_ptr针对new []给出了最简单的实现方式

int main()

{
	std::shared_ptr<Date[]> sp1(new Date[10]);
	std::unique_ptr<Date[]> up1(new Date[10]);

	return 0;
}

但是上面的方案多少具有局限性,malloc的就解决不了,还是给出了一种统一的解决方案:

shared_ptr是在构造的时候给,构造的时候自己去实例化。

定制删除器只要用到的是这个,在构造函数传:(传仿函数、函数指针都可以,自动推导)

int main()

{
	//std::shared_ptr<Date[]> sp1(new Date[10]);
	//std::unique_ptr<Date[]> up1(new Date[10]);

	//定制删除器,可以是函数指针/仿函数/lambda
	
	//将仿函数对象传给DeleteArray<Date>(),底层会将仿函数对象保存起来,释放资源的时候就会调仿函数对象来释放
	shared_ptr<Date> sp2(new Date[5],DeleteArray<Date>());

	//也可以传递函数指针给他,只是这里是模板的函数指针,对T进行实例化一下
	shared_ptr<Date> sp3(new Date[5], DeleteArrayFunc<Date>);

	//也可以用一个lambda
	auto delArrOBJ = [](Date* ptr) {delete[] ptr; };
	shared_ptr<Date> sp4(new Date[5], delArrOBJ);

	//实现其他资源管理的删除器
	shared_ptr<FILE> sp5(fopen("Test.cpp", "r"), Fclose());         //传一个仿函数的匿名对象
	shared_ptr<FILE> sp6(fopen("Test.cpp", "r"), [](FILE* ptr) {    //lambda
		cout << "fclose:" << ptr << endl;
		fclose(ptr);
		});

	//DeleteArray<Date>传类型,传仿函数
	unique_ptr<Date, DeleteArray<Date>> up2(new Date[5]);
	
	//传函数指针做unique_ptr,两个地方都得传递,构造函数得传,这儿也得传
	//函数指针定义的类型不能直接调用,是一个空的函数指针
	unique_ptr<Date,void(*)(Date*)> up3(new Date[5],DeleteArrayFunc<Date>);

	//传lambda也是,两个地方都得传递,decltype可以推导一个对象的类型
	unique_ptr<Date, decltype(delArrOBJ)> up4(new Date[5], delArrOBJ);

	return 0;
}

unique_ptr不是在构造的时候传自动去推导,而是在类模板实例化的时候传:

可不可以让我们自己写的shared_ptr也实现定制删除器呢?

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

		//定制删除器
		//提供一个对应的构造,成员函数也可以是一个函数模板
		template<class D>
		shared_ptr(T* ptr,D del)  //可能是函数指针、仿函数、lambda
			:_ptr(ptr)
			,_pcount(new int(1))
			,_del(del)
		{
		}
		//拷贝构造
		shared_ptr(const shared_ptr<T>& sp)
			:_ptr(sp._ptr)
			, _pcount(sp._pcount)
		{
			++(*_pcount);
		}

		void release()
		{
			if (--(*_pcount) == 0)
			{
				//delete _ptr;
				_del(_ptr);//定制删除器,包装器去进行释放,调用对应的operator()
				delete _pcount;
			}
		}

		//sp1 = sp3 (sp3为sp,sp1为this)
		//赋值考虑的问题最多
		shared_ptr<T>& operator=(const shared_ptr<T>& sp)
		{
			if (_ptr != sp._ptr)
			{
				//避免自己给自己赋值
				release();
				_ptr = sp._ptr;
				_pcount = sp._pcount;
				++(*_pcount);

			}
			return *this;
		}

		~shared_ptr()
		{
			release();
		}

		//像指针一样使用
		T& operator*()
		{
			return *_ptr;
		}

		T* operator->()
		{
			return _ptr;
		}
	private:
		T* _ptr;
		//static int _count;  //静态成员共享同一个,但是不行
		int* _pcount;
		function<void(T*)> _del;  //包装器
	};
}

//函数
template<class T>
void DeleteArrayFunc(T* ptr)
{
	delete[] ptr;
}

//仿函数
template<class T>
class DeleteArray
{
public:
	void operator()(T* ptr)
	{
		delete[] ptr;
	}
};

//lambda
class Fclose
{
public:
	void operator()(FILE* ptr)
	{
		cout << "fclose:" << ptr << endl;
		fclose(ptr);
	}
};

int main()

{
	//std::shared_ptr<Date[]> sp1(new Date[10]);
	//std::unique_ptr<Date[]> up1(new Date[10]);

	//定制删除器,可以是函数指针/仿函数/lambda
	
	//将仿函数对象传给DeleteArray<Date>(),底层会将仿函数对象保存起来,释放资源的时候就会调仿函数对象来释放
	ysy::shared_ptr<Date> sp2(new Date[5],DeleteArray<Date>());

	//也可以传递函数指针给他,只是这里是模板的函数指针,对T进行实例化一下
	ysy::shared_ptr<Date> sp3(new Date[5], DeleteArrayFunc<Date>);

	//也可以用一个lambda
	auto delArrOBJ = [](Date* ptr) {delete[] ptr; };
	ysy::shared_ptr<Date> sp4(new Date[5], delArrOBJ);

	//实现其他资源管理的删除器
	ysy::shared_ptr<FILE> sp5(fopen("Test.cpp", "r"), Fclose());         //传一个仿函数的匿名对象
	ysy::shared_ptr<FILE> sp6(fopen("Test.cpp", "r"), [](FILE* ptr) {    //lambda
		cout << "fclose:" << ptr << endl;
		fclose(ptr);
		});

	
	return 0;
}

运行结果:

此时不传删除器,好像又出问题了:

ysy::shared_ptr<Date> sp7(new Date);

不传删除器调用的是第一个:

shared_ptr(T* ptr = nullptr)
	:_ptr(ptr)
	,_pcount(new int(1))
{}
function<void(T*)> _del; 

这里默认是没有给值,没有给值就用默认值初始化:

里面就没有可调用对象,没有可调用对象调用它时此时就会抛异常,此时给个缺省值就好了,给一个默认的lambda就好了:

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

4.5 完整的代码,仅供参考:


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


// auto_ptr的实现
namespace ysy
{
	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;
	};


	template<class T>
	class unique_ptr
	{
	public:
		explicit 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;  //特点:不让拷贝
		unique_ptr(unique_ptr<T> && sp)
			:_ptr(sp._ptr)
		{
			sp._ptr = nullptr;
		}

		unique_ptr<T>& operator=(unique_ptr<T> && sp)
		{
			delete _ptr;
			_ptr = sp._ptr;
			sp._ptr = nullptr;
		}

	private:
		T* _ptr;
	};

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

		//定制删除器
		//提供一个对应的构造,成员函数也可以是一个函数模板
		template<class D>
		shared_ptr(T* ptr,D del)  //可能是函数指针、仿函数、lambda
			:_ptr(ptr)
			,_pcount(new int(1))
			,_del(del)
		{
		}

		//拷贝构造
		shared_ptr(const shared_ptr<T>& sp)
			:_ptr(sp._ptr)
			, _pcount(sp._pcount)
		{
			++(*_pcount);
		}

		void release()
		{
			if (--(*_pcount) == 0)
			{
				//delete _ptr;
				_del(_ptr);//定制删除器,包装器去进行释放,调用对应的operator()
				delete _pcount;
			}
		}

		//sp1 = sp3 (sp3为sp,sp1为this)
		//赋值考虑的问题最多
		shared_ptr<T>& operator=(const shared_ptr<T>& sp)
		{
			if (_ptr != sp._ptr)
			{
				//避免自己给自己赋值
				release();
				_ptr = sp._ptr;
				_pcount = sp._pcount;
				++(*_pcount);

			}
			return *this;
		}

		~shared_ptr()
		{
			release();
		}

		//像指针一样使用
		T& operator*()
		{
			return *_ptr;
		}

		T* operator->()
		{
			return _ptr;
		}
	private:
		T* _ptr;
		//static int _count;  //静态成员共享同一个,但是不行
		int* _pcount;
		function<void(T*)> _del = [](T* ptr) {delete ptr; };  //包装器
	};
}

//函数
template<class T>
void DeleteArrayFunc(T* ptr)
{
	delete[] ptr;
}

//仿函数
template<class T>
class DeleteArray
{
public:
	void operator()(T* ptr)
	{
		delete[] ptr;
	}
};

//lambda
class Fclose
{
public:
	void operator()(FILE* ptr)
	{
		cout << "fclose:" << ptr << endl;
		fclose(ptr);
	}
};

int main()

{
	//std::shared_ptr<Date[]> sp1(new Date[10]);
	//std::unique_ptr<Date[]> up1(new Date[10]);

	//定制删除器,可以是函数指针/仿函数/lambda
	
	//将仿函数对象传给DeleteArray<Date>(),底层会将仿函数对象保存起来,释放资源的时候就会调仿函数对象来释放
	ysy::shared_ptr<Date> sp2(new Date[5],DeleteArray<Date>());

	//也可以传递函数指针给他,只是这里是模板的函数指针,对T进行实例化一下
	ysy::shared_ptr<Date> sp3(new Date[5], DeleteArrayFunc<Date>);

	//也可以用一个lambda
	auto delArrOBJ = [](Date* ptr) {delete[] ptr; };
	ysy::shared_ptr<Date> sp4(new Date[5], delArrOBJ);

	//实现其他资源管理的删除器
	ysy::shared_ptr<FILE> sp5(fopen("Test.cpp", "r"), Fclose());         //传一个仿函数的匿名对象
	ysy::shared_ptr<FILE> sp6(fopen("Test.cpp", "r"), [](FILE* ptr) {    //lambda
		cout << "fclose:" << ptr << endl;
		fclose(ptr);
		});

	ysy::shared_ptr<Date> sp7(new Date);

	return 0;
}

5. shared_ptr 和 weak_ptr

5.1 shared_ptr循环引用问题

shared_ptr 相比 unique_ptr 好用是好用,但是就是有两大缺陷:

1. shared_ptr循环引用问题 2.shared_ptr的线程安全问题

以下代码就会产生循环引用问题,循环引用问题就会导致内存泄露问题:

struct ListNode
{
	int _data;
	//ListNode* _next;
	//ListNode* _prev;
	
	//将_next变为智能指针,将_prev变为智能指针
	std::shared_ptr<ListNode> _next;
	std::shared_ptr<ListNode> _prev;

	// 这里改成weak_ptr,当n1->_next = n2;绑定shared_ptr时
	// 不增加n2的引用计数,不参与资源释放的管理,就不会形成循环引⽤了

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

int main()
{
	// 循环引用 —— 内存泄漏
	std::shared_ptr<ListNode> n1(new ListNode);
	std::shared_ptr<ListNode> n2(new ListNode);
	cout << n1.use_count() << endl;
	cout << n2.use_count() << endl;
	n1->_next = n2;  //_next原生指针,n2是智能指针对象,那么就将_next变为智能指针
	n2->_prev = n1;  //_prev原生指针,n1是智能指针对象,那么就将_prev变为智能指针
	cout << n1.use_count() << endl;
	cout << n2.use_count() << endl;

	// weak_ptr
	// weak_ptr
	//std::weak_ptr<ListNode> wp(new ListNode);
	return 0;
}

将_next变为智能指针,将_prev变为智能指针,此时就坑了,刚开始的引用计数都是1,怎么都变为了2:

分析过程:

总结一下:

什么样的场景会导致上面的循环引用呢?

两个对象,两个对象里面,各自有一个shared_ptr的智能指针指向对方,只要形成这样的局面就必然会形成循环引用。

如何解决呢?

最初点是因为是原生的指针不是智能指针给不过去,用shared_ptr会形成循环引用,引入weak_ptr来解决这个问题。weak_ptr不遵循RAII

5.2 weak_ptr

  • weak_ptr不支持RAII,也不⽀持访问资源,所以我们看文档发现weak_ptr构造时不支持绑定到资源,只支持绑定到shared_ptr,绑定到shared_ptr时,不增加shared_ptr的引⽤计数,那么就可以解决上述的循环引用问题。

 

struct ListNode
{
	int _data;
	//ListNode* _next;
	//ListNode* _prev;
	
	//将_next变为智能指针,将_prev变为智能指针
	//std::shared_ptr<ListNode> _next;
	//std::shared_ptr<ListNode> _prev;

	// 这里改成weak_ptr,当n1->_next = n2;绑定shared_ptr时
	// 不增加n2的引用计数,不参与资源释放的管理,就不会形成循环引⽤了
	std::weak_ptr<ListNode> _next;
	std::weak_ptr<ListNode> _prev;

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

int main()
{
	// 循环引用 —— 内存泄漏
	std::shared_ptr<ListNode> n1(new ListNode);
	std::shared_ptr<ListNode> n2(new ListNode);
	cout << n1.use_count() << endl;
	cout << n2.use_count() << endl;
	n1->_next = n2;  //_next原生指针,n2是智能指针对象,那么就将_next变为智能指针
	n2->_prev = n1;  //_prev原生指针,n1是智能指针对象,那么就将_prev变为智能指针
	cout << n1.use_count() << endl;
	cout << n2.use_count() << endl;

	// weak_ptr
	// weak_ptr
	//std::weak_ptr<ListNode> wp(new ListNode);
	return 0;
}

weak_ptr最大的特点就是不会增加引用计数,不参与资源的释放管理,还是会互相指向,但是不增加引用计数,就不会形成循环引用,也不会有坑。

运行结果:

weak_ptr的实现:

	template<class T>
	class weak_ptr
	{
	public:
		weak_ptr()
		{ }

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

weak_ptr是不支持解引用的,只是单纯的指向。operator*,operator->都没有重载

template<class T>
class shared_ptr
{
public:
    //……

	int use_count()const
	{
		return *_pcount;
	}

	T* get()const
	{
		return _ptr;
	}

private:
	T* _ptr;
	//static int _count;  //静态成员共享同一个,但是不行
	int* _pcount;
	function<void(T*)> _del = [](T* ptr) {delete ptr; };  //包装器
};
struct ListNode
{
	int _data;
	ysy::weak_ptr<ListNode> _next;
	ysy::weak_ptr<ListNode> _prev;

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

int main()
{
	// 循环引用 —— 内存泄漏
	ysy::shared_ptr<ListNode> n1(new ListNode);
	ysy::shared_ptr<ListNode> n2(new ListNode);
	cout << n1.use_count() << endl;
	cout << n2.use_count() << endl;
	n1->_next = n2;  //_next原生指针,n2是智能指针对象,那么就将_next变为智能指针
	n2->_prev = n1;  //_prev原生指针,n1是智能指针对象,那么就将_prev变为智能指针
	cout << n1.use_count() << endl;
	cout << n2.use_count() << endl;

	// weak_ptr
	// weak_ptr
	//std::weak_ptr<ListNode> wp(new ListNode);
	return 0;
}

运行结果:

库里面实现的 shared_ptr 和 weak_ptr的实现是比我们实现的复杂不少,因为在我们自己实现的方法中weak_ptr是不支持引用计数的,但是在库中也是有引用计数的。

  • weak_ptr也没有重载operator*和operator->等,因为他不参与资源管理,那么如果他绑定的shared_ptr已经释放了资源,那么他去访问资源就是很危险的 (有可能指向的是野指针) 。weak_ptr⽀持expired检查指向的资源是否过期,use_count也可获取shared_ptr的引用计数,weak_ptr想访问资源时,可以调用lock返回⼀个管理资源的shared_ptr,如果资源已经被释放,返回的shared_ptr是⼀个空对象,如果资源没有释放,则通过返回的shared_ptr访问资源是安全的。

std::weak_ptr::expired 判断是否过期,如果过期了就返回true,没有过期就返回false

能拿到std::weak_ptr::use_count,不增加引用计数,但是是可以拿到你的引用计数的,说明远远不是刚刚我们自己实现的那么简单

int main()
{
	std::shared_ptr<string> sp1(new string("11111"));
	std::shared_ptr<string> sp2(sp1);
	std::weak_ptr<string> wp = sp1;

	cout << wp.expired() << endl;
	cout << wp.use_count() << endl;

	return 0;
}

运行结果:

int main()
{
	std::shared_ptr<string> sp1(new string("11111"));
	std::shared_ptr<string> sp2(sp1);
	std::weak_ptr<string> wp = sp1;

	cout << wp.expired() << endl;
	cout << wp.use_count() << endl;

	//sp1和sp2都指向了其他资源,则weak_ptr就过期了
	sp1 = make_shared<string>("22222");
	cout << wp.expired() << endl;
	cout << wp.use_count() << endl;

	sp2 = make_shared<string>("33333");
	cout << wp.expired() << endl;
	cout << wp.use_count() << endl;

	return 0;
}

资源释放不一定是生命周期到了,也有可能是因为被赋值了

此时再去访问的话就有坑,因为已经成为野指针了,如果真的要去访问的话,应该这样去访问,用lock函数,参与管理,但不是自己参与管理,而是增加一个shared_ptr进行管理,lock就是返回一个shared_ptr,相当于就是和shared_ptr一起管理,访问的时候就用shared_ptr去访问,此时就是安全的。

int main()
{
	std::shared_ptr<string> sp1(new string("11111"));
	std::shared_ptr<string> sp2(sp1);
	std::weak_ptr<string> wp = sp1;

	cout << wp.expired() << endl;
	cout << wp.use_count() << endl;

	//sp1和sp2都指向了其他资源,则weak_ptr就过期了
	sp1 = make_shared<string>("22222");
	cout << wp.expired() << endl;
	cout << wp.use_count() << endl;

	sp2 = make_shared<string>("33333");
	cout << wp.expired() << endl;
	cout << wp.use_count() << endl;

	//再去访问的话就有坑,因为已经成为野指针了
	//真的要去访问的话,应该这样去访问,用lock函数
	wp = sp1;
	auto sp3 = wp.lock();
	cout << wp.expired() << endl;
	cout << wp.use_count() << endl;

	sp1 = sp2;

	return 0;
}

运行结果:

从上面的与运行结果中说明引用计数是不能释放的,里面就要求一个方式指向一个引用计数,boost库中是由四五个类来实现的,不增加引用计数,但是要知道现在的引用计数是多少。

weak_ptr想访问这个资源,不是直接访问的,而是lock一下产生另外一个智能指针,跟你共同管理之后,用sp3这个智能指针去修改,sp1也会被修改

int main()
{
	std::shared_ptr<string> sp1(new string("11111"));
	std::shared_ptr<string> sp2(sp1);
	std::weak_ptr<string> wp = sp1;

	cout << wp.expired() << endl;
	cout << wp.use_count() << endl;

	//sp1和sp2都指向了其他资源,则weak_ptr就过期了
	sp1 = make_shared<string>("22222");
	cout << wp.expired() << endl;
	cout << wp.use_count() << endl;

	sp2 = make_shared<string>("33333");
	cout << wp.expired() << endl;
	cout << wp.use_count() << endl;

	//再去访问的话就有坑,因为已经成为野指针了
	//真的要去访问的话,应该这样去访问,用lock函数
	wp = sp1;
	auto sp3 = wp.lock();
	cout << wp.expired() << endl;
	cout << wp.use_count() << endl;

	*sp3 += "###";
	cout << *sp1 << endl;

	sp1 = sp2;

	return 0;
}

运行结果:

6.shared_ptr的线程安全问题

两个智能指针指向同一块资源,它们放在两个线程里面,这个时候就会有线程安全的问题,两个线程同时对引用计数++、--,此时的引用计数就有线程安全的问题,保证引用计数的线程安全有两种方案:
1.加锁
2.原子操作

int main()
{
	ysy::shared_ptr<AA> p(new AA);
	const size_t n = 100000;
	mutex mtx;

	auto func = [&]()
		{
			for (size_t i = 0; i < n; ++i)
			{
				// 这里智能指针拷贝会++计数
				ysy::shared_ptr<AA> copy(p);
				{
					unique_lock<mutex> lk(mtx);
					copy->_a1++;
					copy->_a2++;
				}
			}
		};

	thread t1(func);
	thread t2(func);

	t1.join();
	t2.join();

	cout << p->_a1 << endl;
	cout << p->_a2 << endl;
	cout << p.use_count() << endl;

	return 0;
}

此时,对shared_ptr就需要对引用计数进行加锁,但是这种方式太重
此时还有一种简单的方法:将int* _pcount变为atomic<int>* _pcount,就是线程安全的了,atomic是用单独的用cs实现的类,类去访问的时候去调用它的operator ++和--,它是线程安全的

template<class T>
class shared_ptr
{
public:
    //……
private:
	T* _ptr;
	//int* _pcount;
	atomic<int>* _pcount;

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

智能指针保证它里面的引用计数是线程安全的,但是他指向的资源不是线程安全的,多个线程进行++的时候,要进行加锁,加锁的方式如下图所示:

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

  • Boost库是为C++语⾔标准库提供扩展的⼀些C++程序库的总称,Boost社区建⽴的初衷之⼀就是为C++的标准化⼯作提供可供参考的实现,Boost社区的发起⼈Dawes本⼈就是C++标准委员会的成员之⼀。在Boost库的开发中,Boost社区也在这个⽅向上取得了丰硕的成果,C++11及之后的新语法和库有很多都是从Boost中来的。
  • C++ 98 中产生了第⼀个智能指针auto_ptr。
  • C++ boost给出了更实用的scoped_ptr/scoped_array和shared_ptr/shared_array和weak_ptr等。
  • C++ TR1,引入了shared_ptr等,不过注意的是TR1并不是标准版。
  • C++ 11,引入了unique_ptr和shared_ptr和weak_ptr。需要注意的是unique_ptr对应boost的scoped_ptr。并且这些智能指针的实现原理是参考boost中的实现的。

总结:

智能指针最主要的要去解决的问题就是异常安全,解决C++里面的内存泄漏,所以在项目里为了更好的防止内存泄露,可以大量的使用智能指针,不拷贝使用unique_ptr,要拷贝使用shared_ptr,shared_ptr稍微要注意的是循环计数引用,只要不出现循环引用的场景就不会出现内存泄漏

8. 内存泄露

8.1 什么是内存泄漏,内存泄漏的危害

1、什么是内存泄漏:内存泄漏指因为疏忽或错误造成程序未能释放已经不再使用的内存,⼀般是忘记释放或者发生异常释放程序未能执行导致的。内存泄漏并不是指内存在物理上的消失,而是应用程序分配某段内存后,因为设计错误,失去了对该段内存的控制,因而造成了内存的浪费。(普通程序是不怕内存泄漏的,系统也是不怕内存泄漏的)

int main()
{
	//申请一个1G未释放,这个程序多次运行也没啥危害
	//因为程序马上就结束,进程结束各种资源也就回收了

	char* ptr = new char[1024 * 1024 * 1024];
	cout << (void*)ptr << endl;
	return 0;
}

运行结果:

是能申请成功的。多次运行的话也是会不断的申请成功,但是并没有进行一次的释放。如果一个程序正常的结束,没有释放的内存最终也是会释放的。进程正常结束就算你没释放,他也是会帮你释放的。内存泄漏的危害到底是什么?第一,进程不正常结束(僵尸进程);第二,有很多的服务(例如某团、某滴之类的)是长期进行的,只有在对某个软件进行升级的时候才会关闭,不在正常公告里面的时间点关闭的话就是事故。

2、内存泄漏的危害:普通程序运行⼀会就结束了出现内存泄漏问题也不大,进程正常结束,页表的映射关系解除,物理内存也可以释放。长期运行的程序出现内存泄漏,影响很大,如操作系统、后台服
务、长时间运行的客户端等等,不断出现内存泄漏会导致可用内存不断变少,各种功能响应越来越慢,最终卡死。

8.2 如何检测内存泄漏(了解)

linux下内存泄漏检测:linux的检测工具
windows下使⽤第三方工具:windows下的内存泄露检测工具—VLD

8.3 如何避免内存泄漏

  • 工程前期良好的设计规范,养成良好的编码规范,申请的内存空间记着匹配的去释放。ps:这个理想状态。但是如果碰上异常时,就算注意释放了,还是可能会出问题。需要下⼀条智能指针来管理才有保证。
  • 尽量使用智能指针来管理资源,如果自己场景比较特殊,采用RAII思想自己造个轮子管理。
  • 定期使⽤内存泄漏工具检测,尤其是每次项目快上线前,不过有些⼯具不够靠谱,或者是收费。
  • 总结⼀下:内存泄漏非常常见,解决方案分为两种:1、事前预防型。如智能指针等。2、事后查错型。如泄漏检测工具。
Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐