【C++11】智能指针

文章目录

  • 一.为什么要有智能指针
  • 二.内存泄漏
    • 1. 什么是内存泄漏,内存泄漏的危害
    • 2. 内存泄漏分类
    • 3. 检测内存泄漏
    • 4.如何避免内存泄漏
  • 三.智能指针的原理与使用
    • 1. RAII
    • 2. auto_ptr
  • 四.常用的智能指针
    • 1. unique_ptr
    • 2. shared_ptr
    • 3. 循环引用
    • 4. weak_ptr
    • 5. 定制删除器
  • 五.总结

一.为什么要有智能指针

在传统的C++中,我们通常使用new和delete来手动分配和释放内存。但是,手动管理内存非常容易出错,容易导致内存泄漏或者悬空指针的问题。智能指针的出现就解决了这个问题。

除了常规的忘记释放内存外,在C++引入了异常后,内存泄露的问题愈发严重,大家可以看看下面这段代码,当我输出第二个参数为0,产生整数除以0的异常后,会发生什么问题:

int div()
{int a, b;cin >> a >> b;if (b == 0)throw invalid_argument("除0错误");return a / b;
}
void Func()
{// 1、如果p1这里new 抛异常会如何?// 2、如果p2这里new 抛异常会如何?// 3、如果div调用这里又会抛异常会如何?int* p1 = new int;int* p2 = new int;cout << div() << endl;delete p1;delete p2;cout << "p1、p2释放" << endl;
}
int main()
{try{Func();}catch (exception& e){cout << e.what() << endl;}return 0;
}

在这里插入图片描述

乍一看好像没什么问题,异常被捕获了,并且输出了我们想要的错误信息。

但是p1、p2指向的空间因为异常的跳转,其指向的空间没有得到释放,引发了经典的内存泄漏问题。

二.内存泄漏

1. 什么是内存泄漏,内存泄漏的危害

什么是内存泄漏:

内存泄漏指因为疏忽或错误造成程序未能释放已经不再使用的内存的情况。内存泄漏并不是指内存在物理上的消失,而是应用程序分配某段内存后,因为设计错误,失去了对该段内存的控制,因而造成了内存的浪费。

内存泄漏的危害:

长期运行的程序出现内存泄漏,影响很大,如操作系统、后台服务等等,出现内存泄漏会导致响应越来越慢,最终卡死。

内存泄漏是指针丢了还是内存丢了?

内存泄漏是指在程序运行过程中,动态分配的内存没有被正确释放,导致无法再被使用或者回收。一般情况下,内存泄漏是指程序中的指针丢失了对应的内存地址,从而导致无法释放这块内存。

2. 内存泄漏分类

C/C++程序中一般我们关心两种方面的内存泄漏:

堆内存泄漏(Heap leak)

堆内存指的是程序执行中依据需要分配通过malloc / calloc / realloc / new等从堆中分配的一块内存,用完后必须通过调用相应的 free或者delete 删掉。假设程序的设计错误导致这部分内存没有被释放,那么以后这部分空间将无法再被使用,就会产生Heap Leak。

系统资源泄漏

指程序使用系统分配的资源,比方套接字、文件描述符、管道等没有使用对应的函数释放掉,导致系统资源的浪费,严重可导致系统效能减少,系统执行不稳定。

3. 检测内存泄漏

  • 在linux下内存泄漏检测:linux下几款内存泄漏检测工具

  • 在windows下使用第三方工具:VLD工具说明

  • 其他工具:内存泄漏工具比较

检测工具内部原理:

申请内存用一个容器记录下来,释放内存时,从容器中删除掉。程序结束时,或者没有任务时,容器中的资源可能就是内存泄漏的。

4.如何避免内存泄漏

  1. 工程前期良好的设计规范,养成良好的编码规范,申请的内存空间记着匹配的去释放。ps:这个理想状态。但是如果碰上异常时,就算注意释放了,还是可能会出问题。需要下一条智能指针来管理才有保证。
  2. 采用RAII思想或者智能指针来管理资源。
  3. 有些公司内部规范使用内部实现的私有内存管理库。这套库自带内存泄漏检测的功能选项。
  4. 出问题了使用内存泄漏工具检测。ps:不过很多工具都不够靠谱,或者收费昂贵。

总结一下:

内存泄漏非常常见,解决方案分为两种:

  1. 事前预防型。如智能指针等。
  2. 事后查错型。如泄漏检测工具。

三.智能指针的原理与使用

1. RAII

RAII(Resource Acquisition Is Initialization)是一种利用对象生命周期来控制程序资源(如内存、文件句柄、网络连接、互斥量等等)的简单技术。

即在对象构造时获取资源,接着控制对资源的访问使之在对象的生命周期内始终保持有效,最后在对象析构的时候释放资源。借此,我们实际上把管理一份资源的责任托管给了一个对象。

这种做法有两大好处:

  1. 不需要显式地释放资源
  2. 采用这种方式,对象所需的资源在其生命期内始终保持有效

例如现在,我们使用RAII思想设计释放资源的类:SmartPtr,来解决刚刚的异常跳转问题。

template<class T>
class SmartPtr
{
public:SmartPtr(T* ptr):_ptr(ptr){}~SmartPtr(){cout << "delete:" << _ptr << endl;delete _ptr;}
private:T* _ptr;
};
int div()
{int a, b;cin >> a >> b;if (b == 0)throw invalid_argument("除0错误");return a / b;
}
void Func()
{SmartPtr<int> sp1(new int);SmartPtr<int> sp2(new int);cout << div() << endl;
}
int main()
{try {Func();}catch (const exception& e){cout << e.what() << endl;}return 0;
}

正常输入的结果:

在这里插入图片描述

出现 了异常,出了作用域可以正常调用析构函数来释放空间

在这里插入图片描述

这便是RAII的设计思路,也是智能指针核心设计思想之一,即使用对象管理指针,出作用域自动调用析构函数来释放空间。

但上述的SmartPtr还不能将其称为智能指针,因为它还不具有指针的行为。指针可以解引用,也可以通过->去访问所指空间中的内容,因此:SmartPtr模板类中还得需要将* ->重载下,才可让其

像指针一样去使用。

//1、使用RAII思想设计delete资源的类
//2、像指针一样的行为
template<class T>
class SmartPtr
{
public:SmartPtr(T* ptr):_ptr(ptr){}~SmartPtr(){cout << "delete:" << _ptr << endl;delete _ptr;}//运算符重载两个操作符 --  模拟指针的操作T& operator* (){return *_ptr; //返回_ptr的指向}T* operator->()   //有两个->,其中一个被省略了。{return _ptr; //返回指针}
private:T* _ptr;
};

总结一下智能指针的原理:

  1. RAII特性
  2. 重载operator*opertaor->,具有像指针一样的行为。

上面我们基本实现了smartPtr,但是smartPtr的一大痛点就是拷贝,现在使用smartPtr默认的拷贝构造函来看看smart能不能进行拷贝:

在这里插入图片描述

直接就出现了报错,原因如下:

因为我们没有编写构造函数,所以编译器使用默认构造函数,这就导致其中的_ptr指向了同一空间,在出作用域时,会调用析构函数,这就导致对同一空间的二次释放,导致了报错。
在这里插入图片描述

现在我们就再来解决智能指针拷贝构造的问题:

来看看C++98版本的库中的auto_ptr

2. auto_ptr

C++98版本的库中就提供了auto_ptr的智能指针。

关于auto_ptr的文档解释:std::auto_ptr文档

auto_ptr的实现原理:

  • 管理权转移的思想。

接下来我们使用库中的auto_ptr来实现拷贝,来观察库中的auto_ptr的拷贝构造做了什么事:

在这里插入图片描述
在这里插入图片描述

我们发现:

  • auto_ptr的拷贝构造是将原来的aptr1置为空,然后单独使用aptr2对象来指向该区域。他进行的是资源权转移,是不负责任的拷贝,会导致被拷贝对象悬空。

这里给出auto_ptr拷贝构造函数的实现:

//auto_ptr拷贝的实现--简单的赋值
auto_ptr(auto_ptr<T>& ap):_ptr(ap._ptr)
{ap._ptr=nullptr
}

而auto_ptr的赋值运算符重载同样也是资源转移,例如以下代码:

在这里插入图片描述

而auto_ptr的赋值运算符重载的实现是怎样的呢?

auto_ptr的赋值,会先将被赋值对象指向的内容进行释放,然后进行赋值操作,再将赋值对象的指向置为空,实现如下:

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是一个失败设计,很多公司明确要求不能使用auto_ptr,接下来我们看看常用的三种智能指针。

四.常用的智能指针

在C++98中推出auto_ptr,一直饱受诟病,在C++11中推出了三种功能较完善的智能指针,这也是实际中使用比较频繁的三种智能指针。

1. unique_ptr

C++11版本的库开始提供更靠谱的unique_ptr。

unique_ptr的实现原理:

  • 简单粗暴的防拷贝。

使用场景:

  • 只适用于不需要拷贝的一些场景,功能比较局限。

以下是C++11中对unique_ptr对于拷贝构造函数与赋值重载函数的处理:

在这里插入图片描述

那C++98没有delete,scoped_ptr(unique_ptr的前身)是如何实现的呢?

C++98中对于scoped_ptrde 拷贝构造函数和赋值重载函数的处理:声明但不实现+私有化

在这里插入图片描述

简易版的unique_ptr的实现步骤如下:

  1. 在构造函数中获取资源,在析构函数中释放资源,利用对象的生命周期来控制资源。
  2. *->运算符进行重载,使unique_ptr对象具有指针一样的行为。
  3. 用C++98的方式将拷贝构造函数和拷贝赋值函数声明为私有,或者用C++11的方式在这两个函数后面加上=delete,防止外部调用。

代码如下:

namespace dianxia
{template<class T>class unique_ptr{public://RAIIunique_ptr(T* ptr = nullptr):_ptr(ptr){}~unique_ptr(){if (_ptr != nullptr){cout << "delete: " << _ptr << endl;delete _ptr;_ptr = nullptr;}}//可以像指针一样使用T& operator*(){return *_ptr;}T* operator->(){return _ptr;}//防拷贝unique_ptr(unique_ptr<T>& up) = delete;unique_ptr& operator=(unique_ptr<T>& up) = delete;private:T* _ptr; //管理的资源};
}

2. shared_ptr

引用计数

shared_ptr是C++11中引入的智能指针,shared_ptr通过引用计数的方式解决智能指针的拷贝问题。

  • 每一个被管理的资源都有一个对应的引用计数,通过这个引用计数记录着当前有多少个对象在管理着这块资源。
  • 当新增一个对象管理这块资源时则将该资源对应的引用计数进行++,当一个对象不再管理这块资源或该对象被析构时则将该资源对应的引用计数进行--
  • 当一个资源的引用计数减为0时说明已经没有对象在管理这块资源了,这时就可以将该资源进行释放了。

通过这种引用计数的方式就能支持多个对象一起管理某一个资源,也就是支持了智能指针的拷贝,并且只有当一个资源对应的引用计数减为0时才会释放资源,因此保证了同一个资源不会被释放多次。比如:

int main()
{cl::shared_ptr<int> sp1(new int(1));cl::shared_ptr<int> sp2(sp1);*sp1 = 10;*sp2 = 20;cout << sp1.use_count() << endl; //2cl::shared_ptr<int> sp3(new int(1));cl::shared_ptr<int> sp4(new int(2));sp3 = sp4;cout << sp3.use_count() << endl; //2return 0;
}

说明一下: use_count成员函数,用于获取当前对象管理的资源对应的引用计数。

shared_ptr的模拟实现

简易版的shared_ptr的实现步骤如下:

  1. 在shared_ptr类中增加一个成员变量count,表示智能指针对象管理的资源对应的引用计数。
  2. 在构造函数中获取资源,并将该资源对应的引用计数设置为1,表示当前只有一个对象在管理这个资源。
  3. 在拷贝构造函数中,与传入对象一起管理它管理的资源,同时将该资源对应的引用计数++
  4. 在拷贝赋值函数中,先将当前对象管理的资源对应的引用计数--(如果减为0则需要释放),然后再与传入对象一起管理它管理的资源,同时需要将该资源对应的引用计数++
  5. 在析构函数中,将管理资源对应的引用计数--,如果减为0则需要将该资源释放。
  6. *->运算符进行重载,使shared_ptr对象具有指针一样的行为。

代码如下:

namespace dianxia
{template<class T>class shared_ptr{public://RAIIshared_ptr(T* ptr = nullptr):_ptr(ptr), _pcount(new int(1)){}~shared_ptr(){if (--(*_pcount) == 0){if (_ptr != nullptr){cout << "delete: " << _ptr << endl;delete _ptr;_ptr = nullptr;}delete _pcount;_pcount = nullptr;}}shared_ptr(shared_ptr<T>& sp):_ptr(sp._ptr), _pcount(sp._pcount){(*_pcount)++;}shared_ptr& operator=(shared_ptr<T>& sp){if (_ptr != sp._ptr) //管理同一块空间的对象之间无需进行赋值操作{if (--(*_pcount) == 0) //将管理的资源对应的引用计数--{cout << "delete: " << _ptr << endl;delete _ptr;delete _pcount;}_ptr = sp._ptr;       //与sp对象一同管理它的资源_pcount = sp._pcount; //获取sp对象管理的资源对应的引用计数(*_pcount)++;         //新增一个对象来管理该资源,引用计数++}return *this;}//获取引用计数int use_count(){return *_pcount;}//可以像指针一样使用T& operator*(){return *_ptr;}T* operator->(){return _ptr;}private:T* _ptr;      //管理的资源int* _pcount; //管理的资源对应的引用计数};
}

为什么引用计数需要存放在堆区?

首先,shared_ptr中的引用计数count不能单纯的定义成一个int类型的成员变量,因为这就意味着每个shared_ptr对象都有一个自己的count成员变量,而当多个对象要管理同一个资源时,这几个对象应该用到的是同一个引用计数。

如下图:

在这里插入图片描述

其次,shared_ptr中的引用计数count也不能定义成一个静态的成员变量,因为静态成员变量是所有类型对象共享的,这会导致管理相同资源的对象和管理不同资源的对象用到的都是同一个引用计数。

如下图:

在这里插入图片描述

而如果将shared_ptr中的引用计数count定义成一个指针,当一个资源第一次被管理时就在堆区开辟一块空间用于存储其对应的引用计数,如果有其他对象也想要管理这个资源,那么除了将这个资源给它之外,还需要把这个引用计数也给它。

这时管理同一个资源的多个对象访问到的就是同一个引用计数,而管理不同资源的对象访问到的就是不同的引用计数了,相当于将各个资源与其对应的引用计数进行了绑定。

如下图:

在这里插入图片描述

但同时需要注意,由于引用计数的内存空间也是在堆上开辟的,因此当一个资源对应的引用计数减为0时,除了需要将该资源释放,还需要将该资源对应的引用计数的内存空间进行释放。

3. 循环引用

shared_ptr看着很完美,但是其存在一个循环引用的问题,我们来看看循环引用是怎么出现的吧~

首先我们要引入一下双向链表中节点:

struct Node
{int _val;Node* _next;Node* _prev;~Node(){cout << "~Node()" << endl;}
};

这是使用普通指针的写法,现在我们将其中的普通指针全部改为shared_ptr:

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

这样一个由智能指针创建的双向链表节点就构建好了。

然后我们创建两个节点将其链接起来。

void test_shared_ptr2()
{std::shared_ptr<shared_Node> sn1(new shared_Node);std::shared_ptr<shared_Node> sn2(new shared_Node);sn1->_next = sn2;sn2->_prev = sn1;
}

此时没有任何问题,接下来我们运行一下观察结果:

在这里插入图片描述

发现,为什么我们使用shared_ptr在程序结束的时候没有调用shared_ptr的析构函数?按理来说控制台应该会输出两行 “~shared_Node()” 才对。

接下来我们画图来分析一下两个节点的关系:

在这里插入图片描述

然后当我们程序结束,sn1和sn2智能指针调用各自的析构函数后,其计数器的改变为:

在这里插入图片描述

所以,循环引用导致资源未被释放的原因:

  • 当资源对应的引用计数减为0时对应的资源才会被释放,因此节点1的释放取决于节点2当中的prev成员,而节点2的释放取决于节点1当中的next成员。
  • 而节点1当中的next成员的释放又取决于节点1,节点2当中的prev成员的释放又取决于节点2,于是这就变成了一个死循环,最终导致资源无法释放。

而如果连接结点时只进行一个连接操作,那么当node1和node2的生命周期结束时,就会有一个资源对应的引用计数被减为0,此时这个资源就会被释放,这个释放后另一个资源的引用计数也会被减为0,最终两个资源就都被释放了,这就是为什么只进行一个连接操作时这两个结点就都能够正确释放的原因。

4. weak_ptr

解决循环引用问题

weak_ptr是C++11中引入的智能指针,weak_ptr不是用来管理资源的释放的,它主要是用来解决shared_ptr的循环引用问题的。

  • weak_ptr支持用shared_ptr对象来构造weak_ptr对象,构造出来的weak_ptr对象与shared_ptr对象管理同一个资源,但不会增加这块资源对应的引用计数。

将ListNode中的next和prev成员的类型换成weak_ptr就不会导致循环引用问题了,此时当node1和node2生命周期结束时两个资源对应的引用计数就都会被减为0,进而释放这两个结点的资源。比如:

struct ListNode
{std::weak_ptr<ListNode> _next;std::weak_ptr<ListNode> _prev;int _val;~ListNode(){cout << "~ListNode()" << endl;}
};
int main()
{std::shared_ptr<ListNode> node1(new ListNode);std::shared_ptr<ListNode> node2(new ListNode);cout << node1.use_count() << endl;cout << node2.use_count() << endl;node1->_next = node2;node2->_prev = node1;//...cout << node1.use_count() << endl;cout << node2.use_count() << endl;return 0;
}

通过use_count获取这两个资源对应的引用计数就会发现,在结点连接前后这两个资源对应的引用计数就是1,根本原因就是weak_ptr不会增加管理的资源对应的引用计数。

weak_ptr的模拟实现

简易版的weak_ptr的实现步骤如下:

  1. 提供一个无参的构造函数,比如刚才new ListNode时就会调用weak_ptr的无参的构造函数。
  2. 支持用shared_ptr对象拷贝构造weak_ptr对象,构造时获取shared_ptr对象管理的资源。
  3. 支持用shared_ptr对象拷贝赋值给weak_ptr对象,赋值时获取shared_ptr对象管理的资源。
  4. 对*和->运算符进行重载,使weak_ptr对象具有指针一样的行为。

代码如下:

namespace dianxia
{template<class T>class weak_ptr{public:weak_ptr():_ptr(nullptr){}weak_ptr(const shared_ptr<T>& sp):_ptr(sp.get()){}weak_ptr& operator=(const shared_ptr<T>& sp){_ptr = sp.get();return *this;}//可以像指针一样使用T& operator*(){return *_ptr;}T* operator->(){return _ptr;}private:T* _ptr; //管理的资源};
}

说明一下: shared_ptr还会提供一个get函数,用于获取其管理的资源。

5. 定制删除器

定制删除器的用法

当智能指针对象的生命周期结束时,所有的智能指针默认都是以delete的方式将资源释放,这是不太合适的,因为智能指针并不是只管理以new方式申请到的内存空间,智能指针管理的也可能是以new[]的方式申请到的空间,或管理的是一个文件指针。比如:

struct ListNode
{ListNode* _next;ListNode* _prev;int _val;~ListNode(){cout << "~ListNode()" << endl;}
};
int main()
{std::shared_ptr<ListNode> sp1(new ListNode[10]);   //errorstd::shared_ptr<FILE> sp2(fopen("test.cpp", "r")); //errorreturn 0;
}

这时当智能指针对象的生命周期结束时,再以delete的方式释放管理的资源就会导致程序崩溃,因为以new[]的方式申请到的内存空间必须以delete[]的方式进行释放,而文件指针必须通过调用fclose函数进行释放。

这时就需要用到定制删除器来控制释放资源的方式,C++标准库中的shared_ptr提供了如下构造函数:

template <class U, class D>
shared_ptr (U* p, D del);

参数说明:

  • p:需要让智能指针管理的资源。
  • del:删除器,这个删除器是一个可调用对象,比如函数指针、仿函数、lambda表达式以及被包装器包装后的可调用对象。

当shared_ptr对象的生命周期结束时就会调用传入的删除器完成资源的释放,调用该删除器时会将shared_ptr管理的资源作为参数进行传入。

因此当智能指针管理的资源不是以new的方式申请到的内存空间时,就需要在构造智能指针对象时传入定制的删除器。比如:

template<class T>
struct DelArr
{void operator()(const T* ptr){cout << "delete[]: " << ptr << endl;delete[] ptr;}
};
int main()
{std::shared_ptr<ListNode> sp1(new ListNode[10], DelArr<ListNode>());std::shared_ptr<FILE> sp2(fopen("test.cpp", "r"), [](FILE* ptr){cout << "fclose: " << ptr << endl;fclose(ptr);});return 0;
}

定制删除器的模拟实现

定制删除器的实现问题:

  • C++标准库中实现shared_ptr时是分成了很多个类的,因此C++标准库中可以将删除器的类型设置为构造函数的模板参数,然后将删除器的类型在各个类之间进行传递。
  • 但我们是直接用一个类来模拟实现shared_ptr的,因此不能将删除器的类型设置为构造函数的模板参数。因为删除器不是在构造函数中调用的,而是需要在ReleaseRef函数中进行调用,因此势必需要用一个成员变量将删除器保存下来,而在定义这个成员变量时就需要指定删除器的类型,因此这里模拟实现的时候不能将删除器的类型设置为构造函数的模板参数。
  • 要在当前模拟实现的shared_ptr的基础上支持定制删除器,就只能给shared_ptr类再增加一个模板参数,在构造shared_ptr对象时就需要指定删除器的类型。然后增加一个支持传入删除器的构造函数,在构造对象时将删除器保存下来,在需要释放资源的时候调用该删除器进行释放即可。最好在设置一个默认的删除器,如果用户定义shared_ptr对象时不传入删除器,则默认以delete的方式释放资源。

代码如下:

namespace dianxia
{//默认的删除器template<class T>struct Delete{void operator()(const T* ptr){delete ptr;}};template<class T, class D = Delete<T>>class shared_ptr{private:void ReleaseRef(){_pmutex->lock();bool flag = false;if (--(*_pcount) == 0) //将管理的资源对应的引用计数--{if (_ptr != nullptr){cout << "delete: " << _ptr << endl;_del(_ptr); //使用定制删除器释放资源_ptr = nullptr;}delete _pcount;_pcount = nullptr;flag = true;}_pmutex->unlock();if (flag == true){delete _pmutex;}}//...public:shared_ptr(T* ptr, D del): _ptr(ptr), _pcount(new int(1)), _pmutex(new mutex), _del(del){}//...private:T* _ptr;        //管理的资源int* _pcount;   //管理的资源对应的引用计数mutex* _pmutex; //管理的资源对应的互斥锁D _del;         //管理的资源对应的删除器};
}

这时我们模拟实现的shared_ptr就支持定制删除器了,但是使用起来没有C++标准库中的那么方便。

  • 如果传入的删除器是一个仿函数,那么需要在构造shared_ptr对象时指明仿函数的类型。
  • 如果传入的删除器是一个lambda表达式就更麻烦了,因为lambda表达式的类型不太容易获取。这里可以将lambda表达式的类型指明为一个包装器类型,让编译器传参时自行进行推演,也可以先用auto接收lambda表达式,然后再用decltype来声明删除器的类型。
template<class T>
struct DelArr
{void operator()(const T* ptr){cout << "delete[]: " << ptr << endl;delete[] ptr;}
};
int main()
{//仿函数示例cl::shared_ptr<ListNode, DelArr<ListNode>> sp1(new ListNode[10], DelArr<ListNode>());//lambda示例1cl::shared_ptr<FILE, function<void(FILE*)>> sp2(fopen("test.cpp", "r"), [](FILE* ptr){cout << "fclose: " << ptr << endl;fclose(ptr);});//lambda示例2auto f = [](FILE* ptr){cout << "fclose: " << ptr << endl;fclose(ptr);};cl::shared_ptr<FILE, decltype(f)> sp3(fopen("test.cpp", "r"), f);return 0;
}

五.总结

智能指针常见考点:

  1. 为什么需要智能指针?

忘记释放/异常跳转安全

  1. RAII是什么

将资源交给对象去管理

  1. 智能指针的发展历史

  2. auto_ptr、unique_ptr、shared_ptr、weak_ptr之间的区别和使用场景

  3. 模拟实现各种智能指针

  4. 什么是循环引用,如何解决?解决的原理是什么?

  5. 为什么要使用定制删除器,写个demo。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/91386.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

静态时序分析与时序约束

一、时序分析的基本概念 1. 时钟 理性的时钟模型是一个占空比为50%且周期固定的方波&#xff1a; 实际电路中输入给FPGA的晶振时钟信号是正弦波&#xff1a; 2. 时钟抖动 Clock Jitter&#xff0c;时钟抖动&#xff0c;相对于理想时钟沿&#xff0c;实际时钟存在不随时钟存在…

结构体和数组结合使用

1、定义结构体 struct Student {int num;char name[32]; }; 2、结构体数组定义 #include<iostream> using namespace std;struct Student {int num;char name[32]; }; int main() {//结构体变量复制方式2struct Student arr[2] { {1,"张三"}, {2,"李四…

【Java学习】System.Console使用

背景 在自学《Java核心技术卷1》的过程中看到了对System.Console的介绍&#xff0c;编写下列测试代码&#xff0c; public class ConsoleTest {public static void main(String[] args) {Console cs System.console();String name cs.readLine("AccountInfo: ");…

相互之间差异较大的15种颜色、35种颜色 | 颜色 色卡 色盘 RGB HEX十六进制

任意两个颜色之间&#xff0c;RGB的欧氏距离大于120 1: (211, 44, 31), #d32c1f 2: (205, 140, 149), #CD8C95 3: (67, 107, 173), #436bad 4: (205, 173, 0), #CDAD00 5: (4, 244, 137), #04f489 6: (254, 1, 154), #fe019a 7: (6, 71, 12), #06470c 8: (97, 222, 42), #61de…

基于springboot线上礼品商城

博主主页&#xff1a;猫头鹰源码 博主简介&#xff1a;Java领域优质创作者、CSDN博客专家、公司架构师、全网粉丝5万、专注Java技术领域和毕业设计项目实战 主要内容&#xff1a;毕业设计(Javaweb项目|小程序等)、简历模板、学习资料、面试题库、技术咨询 文末联系获取 项目介绍…

docker通用镜像方法,程序更新时不用重新构建镜像

docker通用镜像方法&#xff0c;程序更新时不用重新构建镜像。更新可执行文件后&#xff0c;重新启动容器就可运行。 功能 1、在demo目录下添加脚本文件start.sh&#xff0c;里面执行demo.jar文件。 2、将demo目录映射到镜像下的 /workspace目录。 3、Dockerfile文件中默认…

使用路由器更改设备IP_跨网段连接PLC

在一些设备IP已经固定,但是需要采集此设备的数据,需要用到跨网段采集 1、将路由器WAN&#xff08;外网拨号口&#xff09;设置为静态IP 2、设置DMZ主机&#xff0c;把DMZ主机地址设置成跨网段的PLC地址 DMZ主机 基本信息. DMZ (Demilitarized Zone)即俗称的非军事区&#xff0…

LC-链表的中间节点(遍历)

LC-链表的中间节点&#xff08;遍历&#xff09; 链接&#xff1a;https://leetcode.cn/problems/middle-of-the-linked-list/description/ 描述&#xff1a;给你单链表的头结点 head &#xff0c;请你找出并返回链表的中间结点。 如果有两个中间结点&#xff0c;则返回第二个…

推断统计(配对样本t检验)

根据题目我们也可以看出配对样本 t 检验是用来检验两配对正态总体的均值是否存在显著差异的一种假设检验方法&#xff0c;虽然是两组数据但是其来自同一部分个体在两个时间段内的测试数据&#xff0c;是同一部份个体&#xff01; 进行配对样本 t 检验之后也是分别做出原假设和备…

2024软考系统架构设计师论文写作要点

一、写作注意事项 系统架构设计师的论文题目对于考生来说&#xff0c;是相对较难的题目。一方面&#xff0c;考生需要掌握论文题目中的系统架构设计的专业知识;另一方面&#xff0c;论文的撰写需要结合考生自身的项目经历。因此&#xff0c;如何将自己的项目经历和专业知识有机…

PAT 1079 Total Sales of Supply Chain

个人学习记录&#xff0c;代码难免不尽人意。 A supply chain is a network of retailers&#xff08;零售商&#xff09;, distributors&#xff08;经销商&#xff09;, and suppliers&#xff08;供应商&#xff09;-- everyone involved in moving a product from supplier…

应用层协议——TCP(上)

文章目录 1. TCP协议1.1 TCP协议段格式1.2 确认应答(ACK)机制1.3 16位窗口大小1.4 6位标志位1.4.1 TCP三次握手 1.5 确认应答(ACK)机制1.6 超时重传机制1.7 连接管理机制1.7.1 理解TIME_WAIT状态1.7.2 理解 CLOSE_WAIT 状态 1. TCP协议 TCP全称为传输控制协议&#xff0c;意思…

基于C#的无边框窗体阴影绘制方案 - 开源研究系列文章

今天介绍无边框窗体阴影绘制的内容。 上次有介绍使用双窗体的方法来显示阴影&#xff0c;这次介绍使用API函数来进行绘制。这里使用的是Windows API函数&#xff0c;操作系统的窗体也是用的这个来进行的绘制。 1、 项目目录&#xff1b; 下面是项目目录&#xff1b; 2、 函数介…

Kubernetes网络模型

Kubernetes 用来在集群上运行分布式系统。分布式系统的本质使得网络组件在 Kubernetes 中是至关重要也不可或缺的。理解 Kubernetes 的网络模型可以帮助你更好的在 Kubernetes 上运行、监控、诊断你的应用程序。 网络是一个很宽泛的领域&#xff0c;其中有许多成熟的技术。对于…

剑指offer-2.1数组

数组 数组可以说是最简单的一种数据结构&#xff0c;它占据一块连续的内存并按照顺序存储数据。创建数组时&#xff0c;我们需要首先指定数组的容量大小&#xff0c;然后根据大小分配内存。即使我们只在数组中存储一个数字&#xff0c;也需要为所有的数据预先分配内存。因此数…

C++ 之动态链接库DLL使用注意事项及C#调用详解

C 之动态链接库DLL使用注意事项及C#调用详解 有时候算法开发完成之后需要封装成动态链接库DLL来进行集成&#xff0c;一方面增加了算法or代码的复用或者广泛使用性&#xff0c;另一方面也起了保密的效果平时封装成DLL之后放到一台新的电脑上会出现问题&#xff0c;所以本文总结…

初识结构体

文章目录 目录1. 结构体类型的声明1.1 结构的基础知识1.2 结构的声明1.3 结构成员的类型1.4 结构体变量的定义和初始化 2. 结构体成员的访问3. 结构体传参 目录 结构体类型的声明结构体初始化结构体成员访问结构体传参 1. 结构体类型的声明 1.1 结构的基础知识 结构是一些值的…

发布属于自己的 npm 包

1 创建文件夹&#xff0c;并创建 index.js 在文件中声明函数&#xff0c;使用module.exports 导出 2 npm 初始化工具包&#xff0c;package.json 填写包的信息&#xff08;包的名字是唯一的&#xff09; npm init 可在这里写包的名字&#xff0c;或者一路按回车&#xff0c;后…

Postgresql 基础使用语法

1.数据类型 1.数字类型 类型 长度 说明 范围 与其他db比较 Smallint 2字节 小范围整数类型 32768到32767 integer 4字节 整数类型 2147483648到2147483647 bigint 8字节 大范围整数类型 -9233203685477808到9223203685477807 decimal 可变 用户指定 精度小…

【Linux初阶】system V消息队列 + system V信号量

文章目录 一、system V消息队列&#xff08;了解&#xff09;二、system V信号量&#xff08;了解&#xff09;1.信号量是什么2.临界资源和临界区3.互斥4.为什么要信号量 三、IPC资源的组织方式结语 一、system V消息队列&#xff08;了解&#xff09; 消息队列提供了一个从一…