C++11 智能指针深度解析:原理、实现与最佳实践

目录

[1.1 智能指针产生的原因](#1.1 智能指针产生的原因)

[1.2 RAII思想:智能指针诞生的基础](#1.2 RAII思想:智能指针诞生的基础)

[1.3 C++标准库的智能指针](#1.3 C++标准库的智能指针)

[1.3.1 auto_ptr](#1.3.1 auto_ptr)

[1.3.2 unique_ptr](#1.3.2 unique_ptr)

[1.3.3 shared_ptr](#1.3.3 shared_ptr)

[1.3.4 weak_ptr](#1.3.4 weak_ptr)

[1.3.5 定制删除器](#1.3.5 定制删除器)

[1.3.6 make_shared](#1.3.6 make_shared)

[1.3.7 智能指针的补充](#1.3.7 智能指针的补充)

[1.4 shared_ptr 和 weak_ptr](#1.4 shared_ptr 和 weak_ptr)

[1.4.1 shared_ptr 循环引用问题](#1.4.1 shared_ptr 循环引用问题)

[1.4.2 weak_ptr](#1.4.2 weak_ptr)

[1.5 shared_ptr的线程安全问题​](#1.5 shared_ptr的线程安全问题)

[1.6 补充知识 ---- 内存泄漏](#1.6 补充知识 ---- 内存泄漏)

[1.6.1 什么是内存泄漏](#1.6.1 什么是内存泄漏)

[1.6.2 内存泄漏的原因和危害](#1.6.2 内存泄漏的原因和危害)

[1.6.3 避免内存泄漏的方法](#1.6.3 避免内存泄漏的方法)

[1.6.4 如何检测内存泄漏](#1.6.4 如何检测内存泄漏)


1.1 智能指针产生的原因

cpp 复制代码
#include <iostream>
#include <string>

double Divide(double x, double y)
{
	if(y == 0)
	{
		throw std::string("发生除0错误");
	}

	return x / y;
}

void Func()
{
	int* arr = new int[10];

	int* brr = new int[10];

	Divide(2, 0);

	std::cout << "delete[] arr" << std::endl;
	delete[] arr;

	std::cout << "delete[] brr" << std::endl;
	delete[] brr;

}

int main()
{
	try
	{
		Func();
	}
	catch (std::string& e)
	{
		std::cout << e << std::endl;
	}
	catch (...)
	{
		std::cout << "未知异常" << std::endl;
	}
	
	return 0;
}

运行结果:

发生除0错误
在上述程序中,我们不仅申请了内存,也释放了内存。但是因为抛异常导致执行流发生改变,后面的delete没有得到执行,就会造成内存泄漏的风险。

第一种解决方案:在即将抛出异常的时候,释放内存。但是new本身也会抛异常,我们也需要额外处理,就会让我们处理起来很麻烦。如下述程序所示:

cpp 复制代码
#include <iostream>
#include <string>

double Divide(double x, double y)
{
	if (y == 0)
	{
		throw std::string("发生除0错误");
	}

	return x / y;
}

void Func()
{
	int *arr = nullptr;
	try
	{
		arr = new int[10];
	}
	catch (const std::exception &e)
	{
		std::cerr << e.what() << '\n';
	}
	int *brr = nullptr;
	try
	{
		brr = new int[10];
	}
	catch (const std::exception &e)
	{
		std::cout << "delete[] arr" << std::endl;
		delete[] arr;
		std::cerr << e.what() << '\n';
	}

	try
	{
		Divide(2, 1);
	}
	catch (const std::string &e)
	{
		std::cout << "delete[] arr" << std::endl;
		delete[] arr;

		std::cout << "delete[] brr" << std::endl;
		delete[] brr;
		throw; // 异常的重新抛出
	}

	std::cout << "delete[] arr" << std::endl;
	delete[] arr;

	std::cout << "delete[] brr" << std::endl;
	delete[] brr;
}

int main()
{
	try
	{
		Func();
	}
	catch (const std::string &e)
	{
		std::cout << e << std::endl;
	}
	catch (...)
	{
		std::cout << "未知异常" << std::endl;
	}

	return 0;
}

可以看出,这个方案能够解决问题,但随着资源越来越多,维护成本就会迅速上升,所以它不是一个好的工程化方案。

第二种解决方案:智能指针应运而生

1.2 RAII思想:智能指针诞生的基础

RAII 是 Resource Acquisition Is Initialization 的缩写,意思为获得资源立即初始化 。它是一种管理资源的类的设计思想 ,核心机制就是抛异常中栈展开的核心机制:栈销毁时,已经构造完成的局部对象一定会被自动析构,它的本质是一种利用类对象生命周期来管理获取到的资源,避免资源泄漏,这里的资源可以是内存、文件指针、网络连接、互斥锁等等。RAII 在获取资源时把资源交给一个类对象,通过类对象对资源进行访问,资源在对象的生命周期内始终保持有效,最后在对象析构的时候释放资源,保障了资源的正常释放,避免资源泄漏问题。
智能指针就是一种基于RAII设计思路实现出来的一种 。但智能指针为了方便资源的访问,所以智能指针会像迭代器一样,重载operator*/operator-> 等运算符,对于管理数组的智能指针,还会提供operator\[\],使其像使用原生指针一样访问资源。

接下来,我们从最简单的智能指针实现开始,逐步理解指针指针是如何利用 RAII 自动管理资源。

cpp 复制代码
template<class T>
class SmartPtr
{
public:
	SmartPtr(T* ptr)
	:_ptr(ptr)
    {}

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

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

	~SmartPtr()
	{
		delete _ptr;
	}

private:
	T* _ptr;
};

1.3 C++标准库的智能指针

C++标准库中的智能指针都在 <memory> 头文件里。

C++标准库中的智能指针有四种:auto_ptr、unique_ptr、shared_ptr、weak_ptr ,除了weak_ptr以外的智能指针都符合RAII和像原生指针一样访问的行为,这三种智能指针主要是解决智能指针拷贝的思路不同。

1.3.1 auto_ptr

auto_ptr 是 C++98 中设计出来的智能指针,它的特点是拷贝时,把被拷贝对象的资源的管理权交给拷贝对象(类似移动语义,掠夺别人的资源),但这是一个非常糟糕的设计,因为它会导致被拷贝对象悬空,后续代码使用它来访问资源时报错,在 C++11 设计出新的智能指针后,强烈建议不要使用 auto_ptr。在 C++11 出来之前很多公司明令禁止使用 auto_ptr。

cpp 复制代码
#include <iostream>
#include <memory>

int main()
{
	std::auto_ptr<int> ap1(new int(5));
	
	printf("ap1中存放的地址: %p, ap1中地址指向的值: %d\n", ap1.get(), *ap1);

	std::auto_ptr<int> ap2(ap1); 

	printf("ap2中存放的地址: %p, ap2中地址指向的值: %d\n", ap2.get(), *ap2);
	printf("ap1中存放的地址: %p\n", ap1.get());

	return 0;
}

运行结果:

ap1中存放的地址: 0x5623ce21beb0, ap1中地址指向的值: 5

ap2中存放的地址: 0x5623ce21beb0, ap2中地址指向的值: 5

ap1中存放的地址: (nil)

模拟实现:

cpp 复制代码
template<class T>
	class auto_ptr
	{
	public:
		auto_ptr(T* ptr)
		:_ptr(ptr)
		{}
        // 拷贝构造和拷贝赋值的参数均不能用const修饰
		auto_ptr(auto_ptr& ap)
		:_ptr(ap._ptr)
		{
			ap._ptr = nullptr;
		}

		auto_ptr& operator=(auto_ptr& ap)
		{
			if(this != &ap)
			{
				delete _ptr;
				_ptr = ap._ptr;
				ap._ptr = nullptr;
			}
			return *this;
		}
		
		T* get() const
		{
			return _ptr;
		}

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

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

		~auto_ptr()
		{
			delete _ptr;
		}

	private:
		T* _ptr;
	};

1.3.2 unique_ptr

unique_ptr 是 C++11 中设计出来的智能指针,它的名字翻译出来是唯一指针 ,它的特点是不支持拷贝,支持移动。如果不需要拷贝,就非常建议使用它。
unique_ptr 和 auto_ptr 的区别是 unique_ptr不允许拷贝,如果发生拷贝,就会编译错误;auto_ptr允许拷贝,被拷贝对象悬空,不会发生编译错误。

cpp 复制代码
#include <iostream>
#include <memory>

int main()
{
	std::unique_ptr<int> up1(new int(5));
	// 编译错误,不支持拷贝
	// std::unique_ptr<int> up2(up1);
	
	// 编译错误, 不支持赋值
	// std::unique_ptr<int> up2;
	// up2 = up1;

	// 支持移动构造和移动拷贝,需注意
	// 被移动的对象的指针悬空,后续访问会发生错误

	std::cout << "up1: " << up1.get() << std::endl;
	std::unique_ptr<int> up2(std::move(up1));

	std::cout << "up1: " << up1.get() << std::endl;
	std::cout << "up2: " << up2.get() << std::endl;

	std::unique_ptr<int> up3;
	up3 = std::move(up2);

	return 0;
}

运行结果:

up1: 0x55b78b948eb0

up1: 0

up2: 0x55b78b948eb0

模拟实现:

cpp 复制代码
template <class T>
	class unique_ptr
	{
	public:
		unique_ptr(T *ptr = nullptr)
			: _ptr(ptr)
		{
		}

		unique_ptr(const unique_ptr &up) = delete;
		unique_ptr &operator=(const unique_ptr &up) = delete;

		unique_ptr(unique_ptr &&up)
			: _ptr(up._ptr)
		{
			up._ptr = nullptr;
		}

		unique_ptr &operator=(unique_ptr &&up)
		{
			if (this != &up)
			{
				delete _ptr;
				_ptr = std::move(up._ptr);
				up._ptr = nullptr;
			}

			return *this;
		}
		T* get()
		{
			return _ptr;
		}
		// 运算符重载... 这里不再模拟实现,可以参考 auto_ptr 的模拟实现
		~unique_ptr()
		{
			delete _ptr;
		}

	private:
		T *_ptr;
	};

1.3.3 shared_ptr

shared_ptr 是 C++11 设计出来的智能指针,它的名字翻译出来是共享指针 ,它的特点是支持拷贝,也支持移动 。如果需要拷贝的场景就使用它。它的底层是用引用计数的方式实现的。

cpp 复制代码
#include <iostream>
#include <memory>

int main()
{
	std::shared_ptr<int> sp1(new int(2));
	std::shared_ptr<int> sp2(sp1);
	std::shared_ptr<int> sp3;
	sp3 = sp1;

	std::cout << "sp1: " << sp1.get() << std::endl;
	std::cout << "sp2: " << sp2.get() << std::endl;
	std::cout << "sp3: " << sp3.get() << std::endl;
	std::cout << sp1.use_count() << std::endl;

	return 0;
}

运行结果:

sp1: 0x55b8c2123eb0

sp2: 0x55b8c2123eb0

sp3: 0x55b8c2123eb0

3

模拟实现:

cpp 复制代码
	template <class T>
	class shared_ptr
	{
	public:
		shared_ptr(T* ptr = nullptr)
		:_ptr(ptr)
		{
			if(_ptr) // _ptr != nullptr
			{
				_count = new int(1);
			}
		}

		shared_ptr(const shared_ptr& sp)
		:_ptr(sp._ptr)
		,_count(sp._count)
		{
			if(_ptr)
				++(*_count);
		}

		shared_ptr& operator=(const shared_ptr& sp)
		{
			if(this != &sp)
			{
				if(_ptr && --(*_count) == 0)
				{
					delete _ptr;
					delete _count;
				}

				_ptr = sp._ptr;
				_count = sp._count;
				if(_ptr)
					++(*_count);
			}
			return *this;
		}

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

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

		T* get() const
		{
			return _ptr;
		}

		size_t use_count()	const
		{
			return _count == nullptr ? 0 : *_count;
		}

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

	private:
		T* _ptr = nullptr;
		int* _count = nullptr;
	};

1.3.4 weak_ptr

weak_ptr 是 C++11 设计出来的智能指针,它的名字翻译出来是弱指针,它不支持 RAII,意味着不能用它直接管理资源,weak_ptr 的产生本质是要解决 shared_ptr 的一个循环引用导致内存泄漏的问题,具体细节下面会讲。

1.3.5 定制删除器

对于标准库中的智能指针析构时默认是进行delete释放资源 ,这也就意味着如果不是new出来的资源,交给智能指针管理,析构时就会崩溃。所以智能指针支持在构造 时给一个删除器,所谓的删除器本质是一个可调用对象,这个可调用对象中实现你想要的释放资源的方式,当构造智能指针时,给了定制删除器,那么智能指针在析构时就会调用删除器来释放资源。

由于new\[\]经常使用,unique_ptr 和 shared_ptr 都特化了一份 \[\] 的版本,使用

unique_ptr<int\[\]> up1(new int5);

shared_ptr<int\[\]> sp1(new int5);

就可以管理 new\[\] 的资源。

cpp 复制代码
#include <iostream>
#include <memory>

template <class T>
void DeleteArrayFunc(T* ptr)
{
	std::cout << "DeleteArrayFunc" << std::endl;
	delete[] ptr;
}

template <class T>
class DeleteArray
{
public:
	void operator()(T* ptr)
	{
		std::cout << "DeleteArray" << std::endl;
		delete[] ptr;
	}
};

class Fclose
{
public:
	void operator()(FILE* pf)
	{
		std::cout << "文件关闭" << std::endl;
		fclose(pf);
	}
};

int main()
{
	// 程序运行崩溃
	// std::unique_ptr<std::string> up1(new std::string[10]);
	// std::shared_ptr<std::string> sp1(new std::string[10]);

	// 解决方案一 ---- 模板特化
	std::unique_ptr<std::string[]> up1(new std::string[10]);
	std::shared_ptr<std::string[]> sp1(new std::string[10]);

	// 解决方案二 ---- 定制删除器

	// 1. 仿函数对象做删除器
	// unique_ptr和shared_ptr支持删除器的方式有所不同​
	// unique_ptr是在类模板参数支持的,shared_ptr是构造函数参数支持的​
	// 这里没有使用相同的方式还是挺坑的​
	// 使用仿函数unique_ptr可以不在构造函数传递,因为仿函数类型构造的对象直接就可以调用​
	// 但是下面的函数指针和lambda的类型不可以​
	std::unique_ptr<std::string[], DeleteArray<std::string>> up2(new std::string[10]);
	std::shared_ptr<std::string[]> sp2(new std::string[10], DeleteArray<std::string>());

	// 2. 函数指针做删除器
	std::unique_ptr<std::string[], void(*)(std::string*)> up3(new std::string[10], DeleteArrayFunc<std::string>);
	std::shared_ptr<std::string[]> sp3(new std::string[10], DeleteArrayFunc<std::string>);
	
	// 3. lambda表达式做删除器
	auto delArr = [](std::string* ptr) 
		{
			std::cout << "lambda" << std::endl;
			delete[] ptr; 
		};
	std::unique_ptr<std::string[], decltype(delArr)> up4(new std::string[10], delArr);
	std::shared_ptr<std::string[]> sp4(new std::string[10], delArr);

	// 实现其他资源管理的删除器
	std::shared_ptr<FILE> sp5(fopen("log.txt", "w"), Fclose());

	std::shared_ptr<FILE> sp6(fopen("log.txt", "w"), [](FILE* ptr)
		{
			std::cout << "文件关闭" << std::endl;
			fclose(ptr);
		});

	return 0;
}

对于 unique_ptr 推荐仿函数对象传定制删除器,对于 shared_ptr 推荐仿函数对象或者lambda对象传定制删除器。

1.3.6 make_shared

shared_ptr 除了支持用指向资源的指针构造,还支持 make_shared 用初始化资源对象的值直接构造。

cpp 复制代码
#include <iostream>
#include <memory>

int main()
{
	std::shared_ptr<std::string> sp1(new std::string("1111"));
	std::shared_ptr<std::string> sp2 = std::make_shared<std::string>("2222");
	auto sp3 = std::make_shared<std::string>("3333");
	return 0;
}

对于 new 出来的对象来构造 shared_ptr ,内存分两次申请,一次申请string对象,一次申请控制块(计数器的对象),对于 make_shared 出来的对象构造 shared_ptr,只做一次内存申请,同时申请 string 对象和 控制块,底层是把对象指针和控制块放到了一个结构体中。

make_shared 的优点

  1. 少一次堆申请 -> 提高效率

  2. cache 更友好 -> 控制块和对象绑定,缓冲命中率高

  3. 异常安全更强 -> new T 成功,但 new 控制块失败,存在内存泄漏风险,make_shared一次性分配,不存在半成功状态

  4. 内存碎片更少 -> 控制块和对象绑定,只需在内存中申请一个struct的空间
    make_shared 的缺点

  5. 不支持定制删除器 -> 需要定制删除器的场景不能使用

  6. 控制块和对象绑定 -> weak_ptr 不会影响对象生命周期,但会延长控制块的生命周期,在make_shared场景下,由于控制块和对象绑定,即使对象得到了释放,也会导致整个结构体内存无法释放。

1.3.7 智能指针的补充

shared_ptr 和 unique_ptr 都支持了 operator bool 的类型转换,如果智能指针对象是一个空对象,没有管理资源,则返回 false,否则返回 true。意味着我们可以把智能指针对象使用 if 判断是否为空。

cpp 复制代码
#include <iostream>
#include <memory>

int main()
{
	std::shared_ptr<int> sp1(new int(2));

	std::shared_ptr<int> sp2;

	if (sp1)
	{
		std::cout << "sp1 is not empty object" << std::endl;
	}
	
	if (!sp2)
	{
		std::cout << "sp2 is empty object" << std::endl;
	}

	return 0;
}

shared_ptr 和 unique_ptr 的构造函数 均使用 explicit 修饰,防止普通指针隐式类型转换成智能指针对象

cpp 复制代码
// 编译错误
std::shared_ptr<int> sp1 = new int(2);

// 这样写的本质是先隐式类型转化构造成一个智能指针对象
// 再将其移动构造sp1
// 而第一步是不被允许的,所以编译错误

1.4 shared_ptr 和 weak_ptr

1.4.1 shared_ptr 循环引用问题

shared_ptr 在大多数情况下管理资源非常合适,支持RAII,也支持拷贝。但是在循环引用的场景下会导致资源没得到释放,产生内存泄漏,所以我们要认识循环引用的场景和资源没释放的原因,并且学会使用 weak_ptr 解决这种问题。

cpp 复制代码
#include <iostream>
#include <memory>

struct ListNode
{
	int _data;
	std::shared_ptr<ListNode> _next;
	std::shared_ptr<ListNode> _prev;
	~ListNode()
	{
		std::cout << "~ListNode()" << std::endl;
	}
};

int main()
{
	// 循环引用 -- 内存泄露​
	std::shared_ptr<ListNode> n1(new ListNode);
	std::shared_ptr<ListNode> n2(new ListNode);
	std::cout << n1.use_count() << std::endl;
	std::cout << n2.use_count() << std::endl;
	n1->_next = n2;
	n2->_prev = n1;
	std::cout << n1.use_count() << std::endl;
	std::cout << n2.use_count() << std::endl;

	return 0;
}

运行结果:

1

1

2

2

从运行结果可以看出,申请的堆内存并没有释放,原因:如果释放了内存,那么智能指针的析构函数会调用节点的析构函数。这就是典型的循环引用产生的内存泄漏问题,接下来让我们一步一步地理解这个过程。

  1. 右边节点什么时候释放? 左边节点的 next 析构后,右边节点就释放了。

  2. next 什么时候析构呢? 左边节点释放,next 就析构了。

  3. 左边节点什么时候释放?右边节点的 prev 析构后,左边节点就释放了。

  4. prev 什么时候析构呢? 右边节点释放,_prev 就析构了。

至此,逻辑上成功形成循环引用,导致内存泄漏。

解决方案:weak_ptr

把ListNode结构体中的_next和_prev改成weak_ptr,由于weak_ptr绑定到shared_ptr时不会增加它的引用计数,_next和_prev不参与资源释放管理逻辑,就成功打破了循环引用,解决了这里的问题​

cpp 复制代码
#include <iostream>
#include <memory>

struct ListNode
{
	int _data;
	/*std::shared_ptr<ListNode> _next;
	std::shared_ptr<ListNode> _prev;*/
	std::weak_ptr<ListNode> _next;
	std::weak_ptr<ListNode> _prev;

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

int main()
{
	std::shared_ptr<ListNode> n1(new ListNode);
	std::shared_ptr<ListNode> n2(new ListNode);
	std::cout << n1.use_count() << std::endl;
	std::cout << n2.use_count() << std::endl;
	n1->_next = n2;
	n2->_prev = n1;
	std::cout << n1.use_count() << std::endl;
	std::cout << n2.use_count() << std::endl;
	return 0;
}

运行结果:

1

1

1

1

~ListNode()

~ListNode()

1.4.2 weak_ptr

weak_ptr 不支持RAII,也不支持访问资源,weak_ptr构造时不支持绑定到资源,只支持绑定到shared_ptr,绑定到shared_ptr时,不增加shared_ptr的引用计数,那么就可以解决上述的循环引用问题。
weak_ptr也没有重载operator*和operator->等,因为它不参与资源管理,如果它绑定的shared_ptr已经释放了资源,那么它去访问资源就是很危险的。weak_ptr支持 expired 检查指向的资源是否过期,use_count也可获取shared_ptr的引用计数 ,weak_ptr想访问资源时,可以调用lock返回一管理资源的shared_ptr,如果资源已经被释放,返回的shared_ptr是一个空对象,如果资源没有释放,则通过返回的shared_ptr访问资源是安全的。

cpp 复制代码
#include <iostream>
#include <memory>

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

	// 检测 wp 指向的资源是否被释放,释放返回true,否则返回false
	std::cout << wp.expired() << std::endl;
	// 获取 shared_ptr 指向 wp 所指向的资源的个数
	std::cout << wp.use_count() << std::endl;

	sp1 = std::make_shared<int>(1);
	sp2 = std::make_shared<int>(3);

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

	wp = sp1;
	// lock 获取 wp 指向资源的指针交给 shared_ptr 对象
	auto sp3 = wp.lock();
	std::cout << wp.expired() << std::endl;
	std::cout << wp.use_count() << std::endl;

	*sp1 *= 10;

	std::cout << *sp1 << std::endl;

	return 0;
}

运行结果:

0

2

1

0

0

2

10

1.5 shared_ptr的线程安全问题​

shared_ptr的引用计数对象在堆上,如果多个shared_ptr对象在多个线程中,进行shared_ptr的拷贝析构时会访问修改引用计数,就会存在线程安全问题,所以shared_ptr引用计数是需要加锁或者原子操作保证线程安全的。我们上面的模拟实现是存在此问题的,我们可以将引用计数从int*改成atomic<int>* 就可以保证引用计数的线程安全问题。但是标准库里面是不存在此问题的
**shared_ptr指向的对象是有线程安全的问题的(标准库也有该问题),但是这个对象的线程安全问题不归shared_ptr管,它也管不了,应该有外层使用shared_ptr的人进行线程安全的控制。**​

1.6 补充知识 ---- 内存泄漏

1.6.1 什么是内存泄漏

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

1.6.2 内存泄漏的原因和危害

对于不是长时间运行的普通程序,运行一段时间结束了,它如果出现内存泄漏的问题,形成的危害并不大。因为进程结束,页表的映射关系解除,将占用的物理内存还给操作系统,操作系统可以将其继续给其他进程使用。对于长期运行的程序,如果出现内存泄漏,危害很大,如操作系统、后台服务、长时间运行的客户端等等,不断出现内存泄漏会导致可用内存不断变少,各种功能响应越来越慢,最终卡死。
对于僵尸状态的进程,它的进程控制块得不到释放,就会一直占用物理内存空间,进而导致内存泄漏,产生操作系统卡顿,后台服务效率慢等问题。

1.6.3 避免内存泄漏的方法

  1. 工程前期良好的设计规范,养成良好的编码规范,申请的内存空间要释放,父进程等待子进程。

  2. 尽量使用智能指针来管理资源,如果自己场景比较特殊,采用RAII思想自己造个轮子管理。​

  3. 定期使用内存泄漏工具检测,尤其是每次项目快上线前,不过有些工具不够靠谱,或者是收费。

总结一下:内存泄漏非常常见,解决方案分为两种:1、事前预防型。如智能指针等。2、事后查错

型。如泄漏检测工具。

1.6.4 如何检测内存泄漏

linux下内存泄漏检测:Linux下几款C++程序中的内存泄露检查工具_c++内存泄露工具分析-CSDN博客文章浏览阅读6w次,点赞48次,收藏308次。本文介绍了几种常用的Linux内存泄露检测工具,包括valgrind、mtrace、dmalloc和Kmemleak。文章详细阐述了每种工具的特点、安装方法及使用步骤,并提供了具体的示例程序。https://blog.csdn.net/gatieme/article/details/51959654windows下内存泄漏检测:

windows下的内存泄露检测工具VLD使用_windows内存泄漏检测工具-CSDN博客文章浏览阅读5.6k次,点赞3次,收藏19次。本文详细介绍了如何在Windows平台利用VLD工具进行C/C++内存泄露检测,包括配置步骤、源码修改、报告解读以及内存泄露案例分析。通过VLD,开发者能快速定位和修复内存泄漏问题,提升代码质量。https://blog.csdn.net/lonely1047/article/details/120038929