前言
🎬 个人主页:@ChenPi
🐻推荐专栏1: 《C++_@ChenPi的博客-CSDN博客》✨✨✨
🔥 推荐专栏2: 《Linux C应用编程(概念类)_@ChenPi的博客-CSDN博客》✨✨✨
🛸推荐专栏3: 《 链表_@ChenPi的博客-CSDN博客 》 ✨✨✨
🌺本篇简介 : 这章我们将学习一下C++的智能指针
仔细算了一下,好像也好多天没有更新C++系列了,因为最近在嵌入式的项目,然后老出Bug,就很烦,就来学点C++来散下心
大概就是操作界面加摄像头做识别,加网络控制,还有温湿度曲线图,这是还没搞好,搞好了在考虑做个专栏了
我们回归主题,这章我们学C++的智能指针,学习智能指针之前,我们先要了解一下什么是指针
本期的主题是 C++ 中的智能指针。
P7 C++指针-CSDN博客
在这里,我们将讨论什么是智能指针以及它能为你做什么。
在之前的内容中,我们讨论了 new、delete 这些内容。
new 是在堆上分配内存,需要 delete 来释放内存,
因为它不会自动的释放内存。
智能指针是实现这一过程自动化的一种方式。
当使用智能指针时,你可以不用调用 delete,
在很多情况下,甚至不需要调用 new。
很多人都倾向于使用不调用 new 或 delete 的这种 C++ 编程风格,
智能指针是实现这个的一种方法。
01 unique_ptr作用域指针
智能指针本质上是一个原始指针的包装,
当你创建一个智能指针,它会调用 new 并为你分配内存,然后基于你使用的智能指针,这些内存会在某一时刻自动释放。
让我们来看看第一个,也是最简单的智能指针 unique_ptr。
unique_ptr 是作用域指针,超出作用域时,它会被销毁。
当你想要一个作用域内使用的指针的时候,
unique_ptr 是很的参考。
让我们来看一个例子。
使用智能指针的第一步要先包含 memory 头文件。
现在我们有了 Entity 类,它有构造函数和析构函数
会在调用时打印相应的信息
这样我们就能了解这些智能指针的行为。
1.1 unique_pt实例化对象
在主函数中,我们在一个空作用域下使用 unique_ptr 来分配一个 Entity。
使用的方法就是如上所示。
这里有个需要注意的点就是, unique_ptr 的构造函数是 explicit 的
没有隐式转换功能, 需要显式的调用构造函数,
这就是使用 unique_ptr 的一种方式,
所以你不能这样写 std::unique_ptr entity()= new Entity()。
然后你就可以像普通的指针一样使用 entity 了。
还有一种方法就是
当然更好的方法是先使用 std::make_unique,再赋值给 unique_ptr。
std::unique_ptr<Entity>entity2 = std::make_unique<Entity>();
这对于 unique_ptr 很重要,主要原因是出于异常安全
因为如果构造函数碰巧抛出异常,它会稍微安全一点
你不会得到一个没有引用的悬空指针,从而造成内存泄漏
这个之后的系列会介绍。
上面的程序调试后就会有这样的效果,申请的这段内存在作用域结束的时候被释放了。
这就是最简单的智能指针了,非常有用,开销很低,它甚至没有开销,
它只是一个栈分配对象,当栈分配对象死亡时,它将调用 delete,释放内存。
1.2 unique_ptr智能指针的小细节
现在出现一个问题,正如上面提到的,如果你想复制这个指针,
使得这个指针可以被传递到一个函数或者另一个类中,然而你却不能复制它。
02 shared_ptr共享指针
如果你真有这样的需求,那么可以使用共享指针 shared_ptr
shared_ptr 有点不同,它还在底层做了很多其它的事情
shared_ptr 实现的方式实际上取决于编译器和你在编译器中使用的标准库
目前来看,它使用的主要是引用计数。
引用计数基本上是一种方法,可以跟踪你的指针有多少个引用,
一旦引用计数达到零,它就被删除了。
举个例子,我创建了一个共享指针 shared_ptr,然后创建了另一个 shared_ptr 来复制它,我的引用计数是2(第一个和第二个,共两个),当第一个挂掉的时候,引用计数器就会减少1。当最后一个挂掉的时候,引用计数回到零,内存被释放。
使用共享指针 shared_ptr的方法很简单,如下举例。
shared_ptr 有点不一样的地方在于,它需要分配另一块内存,叫做控制块,
用来存储引用计数,所以如果你首先创建一个 new Entity,
然后将其传递给 shared_ptr 构造函数,它必须分配,
做2次内存分配(先做一次 new Entity 的分配,然后是 shared_ptr 控制内存块的分配)。
如果你用 make_shared,你能把它们组合起来,这样更有效率。
你不妨按照下面的例子做一个简单的测试,提前想一下调试过程,运行看看是否和你想得一样。
可以看到构造函数已经被创建了
因为构造函数里面的cout已经被打印了
然后当计数为0的时候,它将调用 delete,释放内存
这些都是非常好的工具,你可以经常使用它们,它们可以让你的代码自动化,防止你因为忘记调用 delete 而造成内存泄漏。
shared_ptr 是有一点额外的开销的,因为它的引用计数系统。但是话又说回来,很多倾向于编写自己的内存管理系统的人,也一样会有一些开销。
如果决定了要使用智能指针,建议你优先使用 unique_ptr,然后是 shared_ptr,这才是正确的方式。
测试代码
#include <iostream>
#include <memory>
class Entity
{
private:
public:
Entity()
{
std::cout<<"Entity Created"<<std::endl;
}
~Entity()
{
std::cout<<"Entity Destroyed"<<std::endl;
}
};
int main()
{
{
std::shared_ptr<Entity> entity;
{
std::shared_ptr<Entity> shared_Entity = std::make_unique<Entity>();
entity = shared_Entity;
}
}
return 0;
}