【C++】智能指针

目录

  • 一、智能指针
    • [1.1 智能指针的使用场景分析](#1.1 智能指针的使用场景分析)
    • [1.2 RAII 和 智能指针 的设计思路](#1.2 RAII 和 智能指针 的设计思路)
    • [1.3 C++标准库智能指针的使用](#1.3 C++标准库智能指针的使用)
    • [1.4 智能指针的原理](#1.4 智能指针的原理)
      • [1.4.1 share_ptr 的简单实现](#1.4.1 share_ptr 的简单实现)
      • [1.4.2 重载 `*、->、[]`](#1.4.2 重载 *、->、[])
      • [1.4.3 实现 get、use_count](#1.4.3 实现 get、use_count)
      • [1.4.4 赋值重载](#1.4.4 赋值重载)
      • [1.4.5 支持定制删除器](#1.4.5 支持定制删除器)
    • [1.5 shared_ptr 的循环引用问题](#1.5 shared_ptr 的循环引用问题)
      • [1.5.1 weak_ptr](#1.5.1 weak_ptr)
      • [1.5.2 weak_ptr 的简单实现](#1.5.2 weak_ptr 的简单实现)
      • [1.5.3 weak_ptr 的安全访问](#1.5.3 weak_ptr 的安全访问)
    • [1.6 C++11 和 boost中智能指针的关系](#1.6 C++11 和 boost中智能指针的关系)
    • [1.7 内存泄漏](#1.7 内存泄漏)
      • [1.7.1 什么是内存泄漏,内存泄漏的危害](#1.7.1 什么是内存泄漏,内存泄漏的危害)
      • [1.7.2 如何检测内存泄漏(了解)](#1.7.2 如何检测内存泄漏(了解))
      • [1.7.3 如何避免内存泄漏](#1.7.3 如何避免内存泄漏)

个人主页:矢望

个人专栏:C++LinuxC语言数据结构

一、智能指针

1.1 智能指针的使用场景分析

cpp 复制代码
double Divide(int a, int b)
{
	// 当b == 0时抛出异常
	if (b == 0)
	{
		string s("Divide by zero condition!");
		throw s; // 抛出异常
	}

	return a * 1.0 / b;
}

void func()
{
	int* arr = new int[10];
	int* arr1 = new int[10];
	try
	{
		int a, b;
		cin >> a >> b;
		cout << Divide(a, b) << endl;
	}
	catch (...)
	{
		// 捕获所有异常,释放内存
		cout << "delete[] arr" << endl;
		cout << "delete[] arr1" << endl;
		delete[] arr;
		delete[] arr1;

		throw; // 将异常抛给上一个调用处理
	}
	cout << "delete[] arr" << endl;
	cout << "delete[] arr1" << endl;
	delete[] arr;
	delete[] arr1;
}

int main()
{
	try
	{
		func();
	}
	catch (string& s)
	{
		cout << "main(): " << s << endl;
	}

	return 0;
}

上面是我们上期博客的代码,new申请资源之后,我们也释放资源了,但我们之前因为Divide抛异常导致资源没有被释放,从而内存泄漏,所以我们需要new以后捕获异常,捕获到异常后delete释放申请内存,再把异常抛出。

但这样仍旧可能会内存泄漏,因为new本身就可能会抛异常,如果int* arr1 = new int[10];抛异常了,那么arr的资源就不会被释放,导致内存泄漏,我们如果按照之前的处理方式,还要对new进行捕获异常,进行资源释放,再抛出异常,此时就太麻烦了。而智能指针放到这样的场景里面就让问题简单多了。

1.2 RAII 和 智能指针 的设计思路

RAIIResource Acquisition Is Initialization的缩写,它是一种管理资源的类的设计思想,本质是一种利用类对象生命周期来管理获取到的动态资源,避免资源泄漏,这里的资源可以是内存、文件指针、网络连接、互斥锁等等。RAII在获取资源时把资源委托给一个对象,接着控制对资源的访问,资源在对象的生命周期内始终保持有效,最后在对象析构的时候释放资源,这样保障了资源的正常释放,避免资源泄漏问题

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

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

public:
	T* _ptr;
};

int main()
{
	int* p1 = new int[10];
	SmartPtr<int> sp1(p1);

	return 0;
}

这样就不需要进行手动释放资源了。

使用智能指针解决1.1中的问题:

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

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

public:
	T* _ptr;
};


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

	return a * 1.0 / b;
}

void func()
{
	SmartPtr<int> sp1 = new int[10];
	SmartPtr<int> sp2 = new int[10];

	int a, b;
	cin >> a >> b;
	cout << Divide(a, b) << endl;
}

int main()
{
	int cnt = 2;
	while (cnt--)
	{
		try
		{
			func();
		}
		catch (string& s)
		{
			cout << "main(): " << s << endl;
		}
		cout << endl;
	}

	return 0;
}

不论new语句还是Divide抛异常,资源都会释放:

无论正常结束还是异常结束,析构都可以保障new的资源正常释放。

智能指针类除了满足RAII的设计思路,还要方便资源的访问 ,所以智能指针类还会像迭代器类一样,重载 operator*、operator、operator[] 等运算符,方便访问资源。

cpp 复制代码
template<class T>
class SmartPtr
{
public:
	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];
	}

public:
	T* _ptr;
};

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

智能指针复杂的点在它的拷贝上:

cpp 复制代码
void func()
{
	SmartPtr<int> sp1 = new int[10];
	SmartPtr<int> sp2 = new int[10];
	SmartPtr<int> sp3(sp2);

	int a, b;
	cin >> a >> b;
	cout << Divide(a, b) << endl;
}

这里sp3拷贝sp2,就会导致它们同时指向同一块空间,导致析构了两次,从而程序终止。但这里和vectorstring的解决思路不同,它们是进行深拷贝解决,这是因为它们里面的资源就属于它们,所以在拷贝时,它们想要获取独属于它们的一份,所以可以进行深拷贝开辟空间拷贝数据。

但智能指针这里不同,智能指针只是数据的托管人,这些数据不属于它,所以这里必须进行浅拷贝,那这个问题该如何处理呢?接着往下看。

C++标准库中的智能指针都在<memory>这个头文件下面,我们包含<memory>就可以使用了,智能指针有好几种,除了weak_ptr,它们都符合RAII像指针一样访问的行为,原理上主要是解决智能指针拷贝时的思路不同

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

cpp 复制代码
struct Node
{
	int _x, _y;
	Node(int a = 1, int b = 1)
		:_x(a)
		,_y(b)
	{ }

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

int main()
{
	auto_ptr<Node> p1(new Node);

	// 管理权转移,被拷贝对象悬空
	auto_ptr<Node> p2(p1);
	p2->_x++;
	p1->_x++; // 报错

	return 0;
}

这就很大概率导致没有了解过的程序员误用。

2、unique_ptrC++11设计出来的智能指针,它的名字翻译出来是唯一指针,它的特点是不支持拷贝,只支持移动 。如果不需要拷贝的场景就非常建议使用它。

cpp 复制代码
unique_ptr<Node> p1(new Node);

// 禁止拷贝
//unique_ptr<Node> p2(p1);
// 可以移动
unique_ptr<Node> p2(move(p1));

这里虽然p1也空了,但这是我们主动调用移动构造置空的,是预期结果。而auto_ptr你仅仅是进行了一次拷贝构造,被拷贝的对象就不符合预期的空了。

3、shared_ptrC++11设计出来的智能指针,它的名字翻译出来是共享指针,特点是支持拷贝,也支持移动。如果需要拷贝的场景就需要使用它了。

cpp 复制代码
shared_ptr<Node> p1(new Node);
shared_ptr<Node> p2(p1);
shared_ptr<Node> p3(move(p1));

它既可以拷贝也可以移动,哪是如何做到的呢?它的底层是使用引用计数 的方式实现的。

从上图的运行结果来看,当程序运行结束调用析构时,p2要析构,但还有p3管理数据,所以引用计数减一,等到p3析构时,它是最后一个管理数据的,此时就会调用析构函数。这样就保证了不重复析构。


shared_ptrunique_ptr 都支持了operator bool的类型转换,作用是提供一种自然的方式来检查智能指针是否管理着有效的对象。如果智能指针对象是一个空对象没有管理资源,则返回false,否则返回true,意味着我们可以直接把智能指针对象给if判断是否为空。

cpp 复制代码
shared_ptr<Node> p1(new Node);
shared_ptr<Node> p2(p1);
shared_ptr<Node> p3(move(p1));
unique_ptr<Node> p4(new Node);

//本质是: if(!sp1.operator bool())
if (!p1) cout << "p1 为空" << endl;
if (p2)  cout << "p2 非空" << endl;
if (p4)  cout << "p4 非空" << endl;

调用这个接口是手动释放当前对象,调用空参数的会被置空;调用带参数的会重置为新对象,旧对象会被释放。旧对象是否析构,取决于调用 reset() 后该对象的引用计数是否变为 0


get接口是返回它所管理的指针。

4、weak_ptrC++11设计出来的智能指针,它的名字翻译出来是弱指针,它完全不同于上面的智能指针,不支持RAII,也就不能用它直接管理资源,weak_ptr的产生本质是要解决shared_ptr的一个循环引用导致内存泄漏的问题。具体细节下面我们再讲。

5、智能指针析构时默认进行delete释放资源,但如果不是new出来的资源,交给智能指针管理,析构时就会崩溃。智能指针支持在构造时给一个删除器,删除器本质就是一个可调用对象,这个可调用对象中实现你想要的释放资源方式。

当构造智能指针时,给了定制的删除器,在智能指针析构时就会调用删除器去释放资源。因为new[]经常使用,所以为了简洁,unique_ptrshared_ptr都特化了一份[]的版本,使用时 unique_ptr<Date[]> sp1(new Date[5]);shared_ptr<Date[]> sp2(new Date[5]); 就可以管理new []的资源。

定制删除器给的位置也不一样,shared_ptr是构造函数参数支持的,而unique_ptr是在类模板参数支持的。

定制删除器:
shared_ptr

cpp 复制代码
template<class T>
class DelArr
{
public:
	void operator()(T* ptr)
	{
		delete[] ptr;
	}
};

int main()
{
	// 定制删除器
	shared_ptr<Node> p1(new Node[5], DelArr<Node>());
	shared_ptr<Node> p2(new Node[5], [](Node* ptr) { delete[] ptr; });

	return 0;
}

unique_ptr

cpp 复制代码
template<class T>
class DelArr
{
public:
	void operator()(T* ptr)
	{
		delete[] ptr;
	}
};

int main()
{
	// 定制删除器
	unique_ptr<Node, DelArr<Node>> p1(new Node[5]);
	auto arr = [](Node* ptr) { delete[] ptr; };
	unique_ptr<Node, decltype(arr)> p2(new Node[5], arr);
	// C++ 14 不支持这样写
	//unique_ptr<Node, decltype(arr)> p2(new Node[5]);
	//即使lambda表达式一摸一样,那也是不同的类型
	//unique_ptr<Node, decltype([](Node* ptr) { delete[] ptr; })> p3(new Node[5], [](Node* ptr) { delete[] ptr; });
	// C++20 支持
	//unique_ptr<Node, decltype(arr)> p4(new Node[5]);

	return 0;
}

注意:decltypeC++11 引入的类型推导说明符,它的作用是查询表达式的类型

cpp 复制代码
// C++ 14 不支持这样写
unique_ptr<Node, decltype(arr)> p2(new Node[5]);

为什么C++14 不支持这样写?因为 lambda 表达式在 C++14 中没有默认构造函数

auto lambda = [](Node* ptr) { delete[] ptr; };

// 在 C++14 中,这个类型没有默认构造函数

// 以下代码会编译错误:

// decltype(lambda) another_lambda; // 不能默认构造

unique_ptr 的构造函数要求:
// 可用的构造函数:
unique_ptr(T* p, D d) : ptr_(p), deleter_(d) {} // 需要传递删除器

// 不可用的构造函数(对于lambda类型):
// unique_ptr(T* p) : ptr_(p), deleter_() {} // 需要D有默认构造函数

所以需要显示传递删除器:

cpp 复制代码
// C++14 正确写法:显式传递删除器
unique_ptr<Node, decltype(arr)> p2(new Node[5], arr);

C++20 中,无捕获的 lambda 表达式获得了默认构造函数,所以C++20支持这样写:

cpp 复制代码
// C++20 支持:lambda 现在有默认构造函数
unique_ptr<Node, decltype(arr)> p2(new Node[5]); 

6、make_sharedC++11 引入的智能指针工厂函数,用于创建 shared_ptr 对象。

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

直接构造。

cpp 复制代码
struct Node
{
	int _x, _y;
	Node(int a = 1, int b = 1)
		:_x(a)
		, _y(b)
	{ }

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

int main()
{
	shared_ptr<Node> p1(new Node);
	//shared_ptr<Node> p2 = make_shared<Node>(2, 1);
	auto p2 = make_shared<Node>(2, 1);

	return 0;
}

6、shared_ptrunique_ptr 都得构造函数都使用 explicit 修饰,防止普通指针隐式类型转换成智能指针对象。explicitC++ 中的一个关键字,用于防止构造函数的隐式类型转换。

cpp 复制代码
// 不支持指针隐式类型转换
//shared_ptr<Node> p1 = new Node;
shared_ptr<Node> p1(new Node);
//unique_ptr<Node> p2 = new Node;
unique_ptr<Node> p2(new Node);

1.4 智能指针的原理

auto_ptrunique_ptr这两个智能指针的实现比较简单,了解一下原理即可。auto_ptr的思路是拷贝时转移资源管理权给被拷贝对象,这种思路是不被认可的,也不建议使用。

cpp 复制代码
auto_ptr(auto_ptr<T>& sp)
	:_ptr(sp._ptr)
{
	// 管理权转移
	sp._ptr = nullptr;
}

unique_ptr的思路是不支持拷贝。

cpp 复制代码
unique_ptr(const unique_ptr<T>& sp) = delete;
unique_ptr<T>& operator=(const unique_ptr<T>& sp) = delete;

1.4.1 share_ptr 的简单实现

重点是shared_ptr是如何设计的,尤其是引用计数的设计,主要是一份资源就需要一个引用计数,所以引用计数用静态成员的方式是无法实现的,如果使用静态成员方式就会变成所有的资源无论相同还是不同都公用一个引用计数,这绝对不可行。

需求:一份资源对应一个引用计数

这里要使用堆上动态开辟的方式,构造智能指针对象时,一份资源就要new一个引用计数出来。多个shared_ptr指向同一个资源时就++引用计数,shared_ptr对象析构时就--引用计数,引用计数减到0时,代表当前析构的shared_ptr是最后一个管理资源的对象,此时就析构资源。

cpp 复制代码
namespace SHE
{
	template<class T>
	class shared_ptr
	{
	public:
		shared_ptr(T* ptr = nullptr)
			:_ptr(ptr)
			,_pcount(new int(1))
		{ }

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

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

	private:
		T* _ptr;
		int* _pcount;
	};
}

上面简单实现了一下shared_ptr满足了基本的拷贝、析构等。

cpp 复制代码
SHE::shared_ptr<Node> p1(new Node);
SHE::shared_ptr<Node> p2(p1); // 允许拷贝

1.4.2 重载 *、->、[]

cpp 复制代码
// 像指针一样使用
T& operator*()
{
	return *_ptr;
}

T* operator->()
{
	return _ptr;
}
// operator-> 为什么返回指针?
// 例如:
// SmartPtr<Object> ptr;
// ptr->method();
// 本质是 (ptr.operator->())->method();
// 第一步:ptr.operator->() 返回 Object*
// 第二步:Object*->method() 正常的指针成员访问

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

1.4.3 实现 get、use_count

cpp 复制代码
T* get() const
{
	return _ptr;
}

int use_count() const
{
	return *_pcount;
}

1.4.4 赋值重载

当执行p1 = p2;这段代码时,我们需要先把p1指向的资源的引用计数减一,这里很容易遗忘。

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

// p1 = p2
shared_ptr<T>& operator=(const shared_ptr<T>& s)
{
	if (_ptr != s._ptr) // 避免指向同样资源的智能指针之间赋值
	{
		release();
		_ptr = s._ptr;
		_pcount = s._pcount;
		++(*_pcount);
	}
	return *this;
}

~shared_ptr()
{
	release();
}
cpp 复制代码
SHE::shared_ptr<Node> p1(new Node);
SHE::shared_ptr<Node> p2(p1); // 允许拷贝

SHE::shared_ptr<Node> p3(new Node);
p1 = p3;

1.4.5 支持定制删除器

构造

cpp 复制代码
template<class D>
shared_ptr(T* ptr, D del)
	:_ptr(ptr)
	,_pcount(new int(1))
	,_del(del)
{ }

如果要支持删除器,那么我们类里面一定是需要_del成员的,但是D这个参数只在这个构造函数可以使用,那就意味着不能这样定义:D _del;,那该如何定义呢?

此时我们之前博客讲的包装器就排上用场了,使用包装器就可以声明可调用对象的类型了 ,我们删除器的返回值肯定是void,参数是T*

cpp 复制代码
namespace SHE
{
	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)
			:_ptr(ptr)
			,_pcount(new int(1))
			,_del(del)
		{ }

		// 拷贝构造 --- p1(p2)
		shared_ptr(const shared_ptr<T>& s)
			:_ptr(s._ptr)
			, _pcount(s._pcount)
			,_del(s._del) // 添加删除器
		{
			++(*_pcount);
		}

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

		// p1 = p2
		shared_ptr<T>& operator=(const shared_ptr<T>& s)
		{
			if (_ptr != s._ptr) // 避免指向同样资源的智能指针之间赋值
			{
				release();
				_ptr = s._ptr;
				_pcount = s._pcount;
				++(*_pcount);

				_del = s._del; // 添加删除器
			}
			return *this;
		}

		~shared_ptr()
		{
			release();
		}

		// ...

	private:
		T* _ptr;
		int* _pcount;

		//D _del; // 不可行
		//                        添加缺省值,防止报错
		function<void(T*)> _del = [](T* ptr) { delete ptr; };
	};
}
cpp 复制代码
// 定制删除器
SHE::shared_ptr<Node> p1(new Node[5], [](Node* ptr) { delete[] ptr;});

1.5 shared_ptr 的循环引用问题

shared_ptr大多数情况下管理资源非常合适,支持RAII,也支持拷贝。但是在循环引用的场景下会导致资源没有释放,导致内存泄漏。

cpp 复制代码
struct ListNode
{
	int _data;
	shared_ptr<ListNode> _next;
	shared_ptr<ListNode> _prev;

	ListNode(int data = 1)
		:_data(data)
		,_next(nullptr)
		,_prev(nullptr)
	{ }

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

如下图所示场景:

cpp 复制代码
shared_ptr<ListNode> n1(new ListNode);
shared_ptr<ListNode> n2(new ListNode);

n1->_next = n2;
n2->_prev = n1;

cout << n1.use_count() << endl;
cout << n2.use_count() << endl;


没有资源的释放

1.5.1 weak_ptr

为了解决循环引用,C++11引入了weak_ptr。它的核心特性是不会增加share_ptr的引用计数。

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

cpp 复制代码
struct ListNode
{
	int _data;
	weak_ptr<ListNode> _next;
	weak_ptr<ListNode> _prev;

	ListNode(int data = 1)
		:_data(data)
	{ }

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


weak_ptr不支持RAII,也不支持访问资源,由上面官网中的图片我们发现weak_ptr构造时不支持绑定到资源,只支持绑定到shared_ptr,绑定到shared_ptr时,不增加shared_ptr的引用计数,那么就可以解决上述的循环引用问题。

1.5.2 weak_ptr 的简单实现

cpp 复制代码
namespace SHE
{
	// 不增加引用计数
	template<class T>
	class weak_ptr
	{
	public:
		weak_ptr()
		{ }

		weak_ptr(const shared_ptr<T>& s)
			:_ptr(s.get())
		{ }

		weak_ptr<T>& operator=(const shared_ptr<T>& s)
		{
			_ptr = s.get();

			return *this;
		}
	private:
		T* _ptr = nullptr;
	};
}
cpp 复制代码
struct ListNode
{
	int _data;
	SHE::weak_ptr<ListNode> _next;
	SHE::weak_ptr<ListNode> _prev;

	ListNode(int data = 1)
		:_data(data)
	{ }

	~ListNode()
	{
		cout << "~ListNode()" << endl;
	}
};
cpp 复制代码
SHE::shared_ptr<ListNode> n1(new ListNode);
SHE::shared_ptr<ListNode> n2(new ListNode);

n1->_next = n2;
n2->_prev = n1;

cout << n1.use_count() << endl;
cout << n2.use_count() << endl;

1.5.3 weak_ptr 的安全访问

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

cpp 复制代码
shared_ptr<string> p(new string("abc"));
weak_ptr<string> wp = p;

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

p.reset(); // wp 的对应的资源释放了

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

这时候就过期了。危险操作:直接访问。

cpp 复制代码
// string s = *wp;  // 未定义行为!可能崩溃或数据损坏
// wp->length();    // 未定义行为!

安全的情况:使用lock()

cpp 复制代码
shared_ptr<string> p(new string("abc"));
weak_ptr<string> wp = p;

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

p.reset(); // wp 的对应的资源释放了
//shared_ptr<string> p1 = wp.lock();
// 要访问资源,一定lock出一个新的shared_ptr对象
auto p1 = wp.lock();
cout << wp.expired() << endl;
cout << wp.use_count() << endl;

if (p1)
{
	*p1 += "hello";
	cout << *p1 << endl;
}
else cout << "对象已销毁,无法访问" << endl;

要么给你一个有效的 shared_ptr,要么给你一个空指针,但绝不会让你访问已销毁的对象。

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

Boost库是为C++语言标准库提供扩展的一些C++程序库的总称,Boost社区建立的初衷之一就是为C++的标准化工作提供可供参考的实现,Boost社区的发起人Dawes本人就是C++标准委员会的成员之一。在Boost库的开发中,Boost社区也在这个方向上取得了丰硕的成果,C++11及之后的新语法和库有很多都是从Boost中来的。

C++98 产生了第一个智能指针auto_ptrC++ boost给出了更实用的scoped_ptr/scoped_arrayshared_ptr/shared_arrayweak_ptr等。C++ TR1,引入了shared_ptr等,不过注意的是TR1并不是标准版。C++ 11,引入了unique_ptrshared_ptrweak_ptr。其中unique_ptr对应boostscoped_ptr。并且这些智能指针的实现原理是参考boost中的实现的。

1.7 内存泄漏

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

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

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

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

Linux下内存泄漏检测:Linux下几款C++程序中的内存泄露检查工具
Windows下使用第三方工具:windows内存泄漏检测工具

1.7.3 如何避免内存泄漏

工程前期良好的设计规范,养成良好的编码规范,申请的内存空间记着匹配的去释放。ps:这是理想状态。但是如果碰上异常时,就算注意释放了,还是可能会出问题。需要下一条智能指针来管理才有保证。

尽量使用智能指针来管理资源,如果场景比较特殊,采用RAII思想造个轮子管理。定期使用内存泄漏工具检测,尤其是每次项目快上线前,不过有些工具不够靠谱,或者是收费。

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

总结:
以上就是本期博客分享的全部内容啦!如果觉得文章还不错的话可以三连支持一下,你的支持就是我前进最大的动力!
技术的探索永无止境! 道阻且长,行则将至!后续我会给大家带来更多优质博客内容,欢迎关注我的CSDN账号,我们一同成长!
(~ ̄▽ ̄)~

相关推荐
zzzsde2 小时前
【C++】二叉搜索树
开发语言·c++
无限进步_2 小时前
C语言atoi函数实现详解:从基础到优化
c语言·开发语言·c++·git·后端·github·visual studio
AA陈超2 小时前
ASC学习笔记0022:在不打算修改属性集时访问生成的属性集
c++·笔记·学习·ue5·虚幻引擎·unreal engine
初夏睡觉2 小时前
P1048 [NOIP 2005 普及组] 采药
数据结构·c++·算法
上去我就QWER2 小时前
C++中的堆和栈
开发语言·c++
小欣加油2 小时前
leetcode 1513 仅含1的子串数
c++·算法·leetcode·职场和发展
专注VB编程开发20年2 小时前
.net按地址动态调用VC++DLL将非托管DLL中的函数地址转换为.NET可调用的委托
开发语言·c++·c#·.net
Bin二叉3 小时前
南京大学cpp复习——面向对象第一部分(构造函数,拷贝构造函数,析构函数,移动构造函数,友元)
c++·笔记·学习
大锦终3 小时前
【Linux】高级IO
linux·服务器·网络·c++