C++11智能指针

目录

  • 一、什么是智能指针?
  • 二、为什么需要智能指针?
  • 三、内存泄漏
    • 3.1 什么是内存泄漏?内存泄漏的危害是什么?
    • 3.2 内存泄漏的分类
    • 3.3 如何检测内存泄漏?
    • 3.4 如何避免内存泄漏?
  • 四、智能指针的使用及原理
    • 4.1 RAII
    • 4.2 智能指针的原理
    • 4.3 std::auto_ptr
    • 4.4 std::unique_ptr
    • 4.5 std::shared_ptr
      • 4.5.1 C++11中开始提供更靠谱的并且支持拷贝的shared_ptr
      • 4.5.2 std::shared_ptr的线程安全问题
      • 4.5.3 定制删除器
      • 4.5.3 shared_ptr 的模拟实现代码
      • 4.5.4 std::shared_ptr的循环引用
    • 4.6 C++11和boost中智能指针的关系
  • 五、总结
    • 5.1 智能指针一般都要处理以下问题:
    • 5.2 对比四个智能指针

一、什么是智能指针?

C++中的智能指针是一种特殊类型的指针,它能够自动管理动态分配的内存资源,从而简化内存管理的过程并减少内存泄漏的风险。智能指针通过在对象上使用引用计数技术来跟踪资源的使用情况,并在不再需要该资源时自动释放它。这种自动化的内存管理可以帮助开发人员避免手动释放内存的繁琐工作,并减少因忘记释放内存而导致的错误。

二、为什么需要智能指针?

在“异常”出来之前我们对于动态申请的内存资源只要遵循“谁申请,谁释放”的原则就能够很大程度上减少内存泄漏问题了;但是在有了“异常”之后,因为抛异常会使得执行流不按代码的顺序执行,即执行流通过抛异常会直接跳到catch的地方,所以即使我们在new之后已经写上了detele释放,但是由于抛异常的行为导致我们的程序依然会内存泄漏,而且这个内存泄漏是不可预测的,因为你也不知道什么时候会抛异常,抛异常之后有哪些资源是申请了没有释放的,这些我们都无法预测,而内存泄漏是一个很严重的问题,所以必须要解决,而智能指针就是用来解决这个问题的。

内存泄漏的示例:

double 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 抛异常会如何?答:会导致p1指向的内存没有被释放,内存泄露// 3、如果div调用这里又会抛异常会如何?答:导致p1和p2指向的内存都没有被释放,内存泄漏int* p1 = new int;int* p2 = new int;cout << div() << endl;delete p1;delete p2;
}
int main()
{try{Func();}catch (exception& e){cout << e.what() << endl;}return 0;
}

三、内存泄漏

3.1 什么是内存泄漏?内存泄漏的危害是什么?

什么是内存泄漏:内存泄漏指因为疏忽或错误造成程序未能释放已经不再使用的内存的情况。内存泄漏并不是指内存在物理上的消失,而是应用程序分配某段内存后,因为设计错误,失去了对
该段内存的控制,因而造成了内存的浪费。
内存泄漏的危害:长期运行的程序出现内存泄漏,影响很大,如操作系统、后台服务等等,出现内存泄漏会导致响应越来越慢,最终卡死。

3.2 内存泄漏的分类

C/C++程序中一般我们关心两种方面的内存泄漏:
1、堆内存泄漏(Heap leak)
堆内存指的是程序执行中通过malloc / calloc / realloc / new等从堆中分配的一块内存,用完后必须通过调用相应的 free或者delete 删掉。假设程序的设计错误导致这部分内存没有被释放,那么以后这部分空间将无法再被使用,就会产生Heap Leak。
2、系统资源泄漏
指程序使用系统分配的资源,比如说套接字、文件描述符、管道等没有使用对应的函数释放掉,导致系统资源的浪费,严重可导致系统效能减少,系统执行不稳定。

3.3 如何检测内存泄漏?

使用一些第三方工具检测。

3.4 如何避免内存泄漏?

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

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

四、智能指针的使用及原理

4.1 RAII

RAII(Resource Acquisition Is Initialization)是一种利用对象生命周期来控制程序资源(如内存、文件句柄、网络连接、互斥量等等)的技术。
在对象构造时获取资源,接着控制对资源的访问使之在对象的生命周期内始终保持有效,最后在对象析构的时候释放资源。借此,我们实际上把管理一份资源的责任托管给了一个对象。
这种做法有两大好处:
1、不需要显式地释放资源。
2、采用这种方式,对象所需的资源在其生命期内始终保持有效。

//使用RAII思想设计的一个SmartPtr类
template <class T>
class SmartPtr
{
public://构造函数SmartPtr(T* ptr = nullptr):_ptr(ptr){cout << _ptr << endl;}//析构函数~SmartPtr(){if (_ptr){cout << "~SmartPtr()" << endl;delete _ptr;}}
private:T* _ptr;
};

4.2 智能指针的原理

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


//使用RAII思想设计的一个简单的智能指针,要求要能像指针一样使用
template <class T>
class SmartPtr
{
public://构造函数SmartPtr(T* ptr = nullptr):_ptr(ptr){cout << _ptr << endl;}//析构函数~SmartPtr(){if (_ptr){cout << "~SmartPtr()" << endl;delete _ptr;}}T& operator*(){return *_ptr;}T* operator->(){return _ptr;}
private:T* _ptr;
};int main()
{SmartPtr<int> ptr(new int(2));//SmartPtr<int> ptr1(ptr);//像指针一样使用cout << *ptr << endl;*ptr = 1;cout << *ptr << endl;SmartPtr<pair<string, string>> ptr1(new pair<string, string>("科比","kb"));//注意:这里其实是有两个->的,但是在语法上为了可读性,省略了一个->cout << ptr1->first << ":" << ptr1->second << endl;return 0;
}

所以智能指针的原理就是:
1、RAII特性
2、重载operator*和operator->,使它可以像指针一样使用。

4.3 std::auto_ptr

std::auto_ptr

C++98版本的库中就提供了auto_ptr的智能指针。
auto_ptr的实现原理:管理权转移的思想.

拷贝构造函数:
在这里插入图片描述
赋值重载函数:
在这里插入图片描述

	//缺陷:因为auto_ptr的本质是管理权转移,所以拷贝构造时会导致被拷贝//的对象的指针变为空指针,再访问该指针就会出问题,所以不能拷贝和赋值template <class T>class auto_ptr{public://构造函数auto_ptr(T* ptr):_ptr(ptr){}//析构函数~auto_ptr(){if (_ptr){cout << "~auto_ptr()" << endl;delete _ptr;}}//拷贝构造函数auto_ptr(auto_ptr<T>& aptr){//管理权转移_ptr = aptr._ptr;aptr._ptr = nullptr;}auto_ptr<T>& operator=(auto_ptr<T>& aptr){if (&aptr != this){if (_ptr){delete _ptr;}//管理权转移_ptr = aptr._ptr;aptr._ptr = nullptr;}return *this;}T& operator*(){return *_ptr;}T* operator->(){return _ptr;}private:T* _ptr;};int main(){kb::auto_ptr<int> ap1 = new int(1);kb::auto_ptr<int> ap3 = new int(3);//赋值之后ap1变成了nullptr,不能再访问ap1指向的资源了ap3 = ap1;//	kb::auto_ptr<int> ap1 = new int(1);//	kb::auto_ptr<int> ap3(ap1);//管理权转移,ap1变为nullptr,不能再访问ap1指向的内容return 0;}

4.4 std::unique_ptr

C++11中开始提供更靠谱的unique_ptr。
std::unique_ptr

原理:简单粗暴 – 防拷贝

	// C++11库才更新智能指针出来// C++11出来之前,boost搞出了更好用的scoped_ptr/shared_ptr/weak_ptr// C++11将boost库中智能指针精华部分吸收了过来// C++11->unique_ptr/shared_ptr/weak_ptr// unique_ptr/scoped_ptr// 原理:简单粗暴 -- 防拷贝//unique_ptr是唯一的指针,本质是防拷贝,缺陷也是不能拷贝和赋值template <class T>class unique_ptr{public://构造函数unique_ptr(T* up):_ptr(up){}//析构函数~unique_ptr(){if (_ptr){cout << "~unique_ptr()" << endl;delete _ptr;}}//因为unique_ptr是防拷贝的,所以直接把拷贝构造函数和赋值重载函数删除即可unique_ptr(const unique_ptr<T>& up) = delete;unique_ptr<T>& operator=(const unique_ptr& up) = delete;T& operator*(){return *_ptr;}T* operator->(){return _ptr;}private:T* _ptr;};int main(){kb::unique_ptr<int> ptr(new int(1));cout << *ptr << endl;(*ptr)++;cout << *ptr << endl;return 0;}

在这里插入图片描述

4.5 std::shared_ptr

4.5.1 C++11中开始提供更靠谱的并且支持拷贝的shared_ptr

std::shared_ptr

shared_ptr的原理:是通过引用计数的方式来实现多个shared_ptr对象之间共享资源。

  1. shared_ptr在其内部,给每个资源都维护了着一份计数,用来记录该份资源被几个对象共享。
  2. 在对象被销毁时(也就是析构函数调用),就说明自己不使用该资源了,对象的引用计数减1。
  3. 如果引用计数是0,就说明自己是最后一个使用该资源的对象,必须释放该资源;
  4. 如果不是0,就说明除了自己还有其他对象在使用该份资源,不能释放该资源,否则其他对象就成野指针了。

4.5.2 std::shared_ptr的线程安全问题

shared_ptr的线程安全分为两方面:

  1. 智能指针对象中引用计数是多个智能指针对象共享的,两个线程中智能指针的引用计数同时++或–,这个操作不是原子的,引用计数原来是1,++了两次,可能还是2,这样引用计数就错乱了。会导致资源未释放或者程序崩溃的问题。所以智能指针中引用计数++、- -是需要加锁的,也就是说引用计数的操作是线程安全的。
  2. 智能指针管理的对象存放在堆上,两个线程中同时去访问,会导致线程安全问题。
    第一个线程安全问题是智能指针可以解决的,就是访问引用计数时加锁,但是第二个线程安全问题智能指针是管不了的,需要由调用的人确保堆上的资源的访问是线程安全的。

4.5.3 定制删除器

如果不是new出来的对象如何通过智能指针管理呢?其实shared_ptr设计了一个删除器来解决这个问题。
具体细节可以看以下shared_ptr的实现代码。

4.5.3 shared_ptr 的模拟实现代码

	template <class T>class shared_ptr{public://要求://1、RAII//2、像指针一样//构造函数shared_ptr(T* ptr = nullptr):_ptr(ptr), _pRefCount(new int(1)),_pmtx(new mutex){}//构造函数//这是一个函数模板,通过传过来的参数自动推导类型,因为_del是一个包装器,//所以无论D是什么类型的删除器,_del都能接收template <class T,class D>shared_ptr(T* ptr , D del):_ptr(ptr),_pRefCount(new int(1)),_pmtx(new mutex),_del(del){}// 这里的返回值千万不能写成shared_ptr<T>,否则程序会崩溃// 因为写成shared_ptr<T>相当于拷贝构造了一个shared_ptr<T>,// 但是这个shared_ptr<T>并没有开辟空间,而是用_ptr+n位置的// 指针来构造,所以这个临时对象在析构的时候相当于把原来的_ptr+n// 的空间析构了,而这个空间本身就不属于这个临时对象,并且指针也// 只能从其实位置析构,不能从中间位置析构,所以这里写曾shared_ptr<T>// 是一定会崩溃的//shared_ptr<T> operator+(int n)T* operator+(int n){return _ptr + n;}//析构函数~shared_ptr(){//在每一个修改_pRefCount的地方都要先加锁,保证访问_pRefCount是线程安全_pmtx->lock();bool flag = false;if (--(*_pRefCount) == 0 && _ptr){cout << "~shared_ptr()" << endl;_del(_ptr);delete _pRefCount;flag = true;}//解锁_pmtx->unlock();//如果这是最后一个指向这块资源的指针,即_ptr和_pRefCount都释放了,那么应该把_pmtx锁也释放掉if (flag == true){delete _pmtx;}}//拷贝构造函数//sp3(sp1)shared_ptr(const shared_ptr<T>& sp): _ptr(sp._ptr), _pRefCount(sp._pRefCount),_pmtx(sp._pmtx){cout << "shared_ptr(const shared_ptr<T>& sp)" << endl;//在每一个修改_pRefCount的地方都要先加锁,保证访问_pRefCount是线程安全_pmtx->lock();(*_pRefCount)++;//解锁_pmtx->unlock();}//赋值重载函数shared_ptr<T>& operator=(const shared_ptr<T>& sp){//首先一定要判断不能给自己赋值,原因在于下一个if语句解释。//这里能用if(this!=&sp)吗?可以是可以,但是有一种场景会//做多余的工作,具体场景看图解if (_ptr == sp._ptr){return *this;}//也可以通过_pRefCount 是否等于 sp._pRefCount判断是不是给自己赋值//if (_pRefCount == sp._pRefCount)//{//	return *this;//}cout << "shared_ptr<T> operator=(const shared_ptr<T>& sp)" << endl;//在每一个修改_pRefCount的地方都要先加锁,保证访问_pRefCount是线程安全_pmtx->lock();//如果自己给自己赋值,那么如果此时只有自己一个指针指向这块空间//那么--(*_pRefCount)就会等于0,那么进入if会把_ptr和_pRefCount释放掉,//自己给自己赋值说明sp也是自己,所以sp._ptr会把已经释放的空间又赋// 值给_ptr,后续访问_ptr的时候就是非法访问了,会导致程序崩溃if (--(*_pRefCount) == 0){_del(_ptr);delete _pRefCount;}_ptr = sp._ptr;_pRefCount = sp._pRefCount;(*_pRefCount)++;//解锁_pmtx->unlock();return *this;}T& operator*() const{return *_ptr;}T* operator->() const{return _ptr;}T* get() const{return _ptr;}int UseCount() const{return *_pRefCount;}private:T* _ptr;//1、这里能否直接用一个int _pRefCount,不能,因为每个指针应该只有一个引用计数,//如果每一个对象都有一个引用计数,那么引用计数就失去了它的作用了//2、那么就用一个静态的static int _pRefCount变量,静态变量保证每一个类只有一个,//所以所有的对象都是共用一个_pRefCount变量的。也不能用静态变量,为什么?因为不同的//对象可能管理不同的资源,如果所有的对象都共用一个引用计数的话那么指向不同资源的//指针的引用计数也会混到一起,所以不能用静态变量//3、所以最好的方案就是每一块内存资源对应一个引用计数,在拷贝构造和赋值的时候对//指向同一块资源的引用计数++即可,如何保证每一块内存资源对应一个引用计数呢?可以//在构造函数的时候动态开辟一个保存引用计数的变量,因为每个对象只会调用一次构造函数,//所以后面的拷贝构造和赋值都只会对同一个引用计数进行操作。int* _pRefCount = nullptr;//因为引用计数有可能是多个对象共享的,所以当一个对象在修改引用计数的时候,其它对象//不能同时修改引用计数,否则可能会导致引用计数不准确,出现程序崩溃或者内存泄漏的问题mutex* _pmtx = nullptr;//定制删除器// 默认情况下,释放内存用的都是delete,所以缺省值就是delete释放,//但是当我们的内存不是new出来的时候,比如是new[]的,或者fopen的,这时需要//正确地释放内存需要用对应的关键字,例如fopen对应fclose,new[]对应delete[],//我们发现,虽然指针的释放的关键字会有区别,但是释放的函数都是只需要传一个T*指针的指针//就可以了。而删除器本质就是一个可调用对象,可调用对象分为函数指针,仿函数//和lambda表达式,我们并不确定用户会使用哪一种可调用对象作为删除器,// 但是删除器的参数和返回值类型都是一样的(无返回值),对于malloc,释放函数为free(ptr),// 无返回值;对于new,释放函数为delete ptr,无返回值;对于new[],释放函数为delete[] ptr,// 无返回值;对于fopen,释放函数为fclose,无返回值;所以这时我们的包装器就派上用场了,因为// 包装器就是包装具有同样参数和返回值的可调用对象的,所以我们可以用包装器接收用户传过来的删除器,// 删除器有对应的释放内存的方法,在智能指针析构的时候通过删除器就可以做到正确地释放内存了function<void(T*)> _del = [](T* ptr) {delete ptr;ptr = nullptr; };};// 仿函数的删除器
template<class T>
struct FreeFunc 
{void operator()(T* ptr){cout << "free:" << ptr << endl;free(ptr);}
};template<class T>
struct DeleteArrayFunc 
{void operator()(T* ptr){cout << "delete[]" << ptr << endl << endl;delete[] ptr;}
};int main()
{kb::shared_ptr<int> ptr1(new int[10], DeleteArrayFunc<int>());*ptr1 = 1;cout << *ptr1 << endl;*(ptr1 + 1) = 2;cout << *(ptr1 + 1) << endl;kb::shared_ptr<int> ptr2((int*)malloc(100), [](int* p) {cout << "free:" << p << endl << endl;free(p);p = nullptr;});kb::shared_ptr<FILE> ptr((FILE*)fopen("test.txt", "w"), [](FILE* fp) {cout << "fclose:" << fp << endl << endl;fclose(fp);fp = nullptr;});return 0;
}

4.5.4 std::shared_ptr的循环引用

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

	template <class T>class shared_ptr{public://要求://1、RAII//2、像指针一样//构造函数shared_ptr(T* ptr = nullptr):_ptr(ptr), _pRefCount(new int(1)),_pmtx(new mutex){}//构造函数//这是一个函数模板,通过传过来的参数自动推导类型,因为_del是一个包装器,//所以无论D是什么类型的删除器,_del都能接收template <class T,class D>shared_ptr(T* ptr , D del):_ptr(ptr),_pRefCount(new int(1)),_pmtx(new mutex),_del(del){}// 这里的返回值千万不能写成shared_ptr<T>,否则程序会崩溃// 因为写成shared_ptr<T>相当于拷贝构造了一个shared_ptr<T>,// 但是这个shared_ptr<T>并没有开辟空间,而是用_ptr+n位置的// 指针来构造,所以这个临时对象在析构的时候相当于把原来的_ptr+n// 的空间析构了,而这个空间本身就不属于这个临时对象,并且指针也// 只能从其实位置析构,不能从中间位置析构,所以这里写曾shared_ptr<T>// 是一定会崩溃的//shared_ptr<T> operator+(int n)T* operator+(int n){return _ptr + n;}//析构函数~shared_ptr(){//在每一个修改_pRefCount的地方都要先加锁,保证访问_pRefCount是线程安全_pmtx->lock();bool flag = false;if (--(*_pRefCount) == 0 && _ptr){cout << "~shared_ptr()" << endl;_del(_ptr);delete _pRefCount;flag = true;}//解锁_pmtx->unlock();//如果这是最后一个指向这块资源的指针,即_ptr和_pRefCount都释放了,那么应该把_pmtx锁也释放掉if (flag == true){delete _pmtx;}}//拷贝构造函数//sp3(sp1)shared_ptr(const shared_ptr<T>& sp): _ptr(sp._ptr), _pRefCount(sp._pRefCount),_pmtx(sp._pmtx){cout << "shared_ptr(const shared_ptr<T>& sp)" << endl;//在每一个修改_pRefCount的地方都要先加锁,保证访问_pRefCount是线程安全_pmtx->lock();(*_pRefCount)++;//解锁_pmtx->unlock();}//赋值重载函数shared_ptr<T>& operator=(const shared_ptr<T>& sp){//首先一定要判断不能给自己赋值,原因在于下一个if语句解释。//这里能用if(this!=&sp)吗?可以是可以,但是有一种场景会//做多余的工作,具体场景看图解if (_ptr == sp._ptr){return *this;}//也可以通过_pRefCount 是否等于 sp._pRefCount判断是不是给自己赋值//if (_pRefCount == sp._pRefCount)//{//	return *this;//}cout << "shared_ptr<T> operator=(const shared_ptr<T>& sp)" << endl;//在每一个修改_pRefCount的地方都要先加锁,保证访问_pRefCount是线程安全_pmtx->lock();//如果自己给自己赋值,那么如果此时只有自己一个指针指向这块空间//那么--(*_pRefCount)就会等于0,那么进入if会把_ptr和_pRefCount释放掉,//自己给自己赋值说明sp也是自己,所以sp._ptr会把已经释放的空间又赋// 值给_ptr,后续访问_ptr的时候就是非法访问了,会导致程序崩溃if (--(*_pRefCount) == 0){_del(_ptr);delete _pRefCount;}_ptr = sp._ptr;_pRefCount = sp._pRefCount;(*_pRefCount)++;//解锁_pmtx->unlock();return *this;}T& operator*() const{return *_ptr;}T* operator->() const{return _ptr;}T* get() const{return _ptr;}int UseCount() const{return *_pRefCount;}private:T* _ptr;//1、这里能否直接用一个int _pRefCount,不能,因为每个指针应该只有一个引用计数,//如果每一个对象都有一个引用计数,那么引用计数就失去了它的作用了//2、那么就用一个静态的static int _pRefCount变量,静态变量保证每一个类只有一个,//所以所有的对象都是共用一个_pRefCount变量的。也不能用静态变量,为什么?因为不同的//对象可能管理不同的资源,如果所有的对象都共用一个引用计数的话那么指向不同资源的//指针的引用计数也会混到一起,所以不能用静态变量//3、所以最好的方案就是每一块内存资源对应一个引用计数,在拷贝构造和赋值的时候对//指向同一块资源的引用计数++即可,如何保证每一块内存资源对应一个引用计数呢?可以//在构造函数的时候动态开辟一个保存引用计数的变量,因为每个对象只会调用一次构造函数,//所以后面的拷贝构造和赋值都只会对同一个引用计数进行操作。int* _pRefCount = nullptr;//因为引用计数有可能是多个对象共享的,所以当一个对象在修改引用计数的时候,其它对象//不能同时修改引用计数,否则可能会导致引用计数不准确,出现程序崩溃或者内存泄漏的问题mutex* _pmtx = nullptr;//定制删除器// 默认情况下,释放内存用的都是delete,所以缺省值就是delete释放,//但是当我们的内存不是new出来的时候,比如是new[]的,或者fopen的,这时需要//正确地释放内存需要用对应的关键字,例如fopen对应fclose,new[]对应delete[],//我们发现,虽然指针的释放的关键字会有区别,但是释放的函数都是只需要传一个T*指针的指针//就可以了。而删除器本质就是一个可调用对象,可调用对象分为函数指针,仿函数//和lambda表达式,我们并不确定用户会使用哪一种可调用对象作为删除器,// 但是删除器的参数和返回值类型都是一样的(无返回值),对于malloc,释放函数为free(ptr),// 无返回值;对于new,释放函数为delete ptr,无返回值;对于new[],释放函数为delete[] ptr,// 无返回值;对于fopen,释放函数为fclose,无返回值;所以这时我们的包装器就派上用场了,因为// 包装器就是包装具有同样参数和返回值的可调用对象的,所以我们可以用包装器接收用户传过来的删除器,// 删除器有对应的释放内存的方法,在智能指针析构的时候通过删除器就可以做到正确地释放内存了function<void(T*)> _del = [](T* ptr) {delete ptr;ptr = nullptr; };}; weak_ptr不是RAII智能指针,专门为了解决shared_ptr的循环引用的// 问题,weak_ptr不参与修改引用计数和资源的释放,允许访问资源template <class T>class weak_ptr{public:weak_ptr():_ptr(nullptr){}//拷贝构造不增加sp对象本身的引用计数,但是weak_ptr可以正常访问sp指向的对象weak_ptr(const shared_ptr<T>& sp):_ptr(sp.get()){}//赋值也不增加sp对象本身的引用计数,但是weak_ptr可以正常访问sp指向的对象weak_ptr<T>& operator=(const shared_ptr<T>& sp){if (_ptr != sp.get()){_ptr = sp.get();}return *this;}T& operator*(){return *_ptr;}T* operator->(){return _ptr;}private:T* _ptr;};//shared_ptr智能指针是线程安全的吗?//是的,引用计数的加减是加锁保护的。但是指向资源不是线程安全的,因为指//向堆上资源的线程安全问题是访问的人处理的,智能指针不管,也管不了。引用//计数的线程安全问题,是智能指针要处理的。//循环引用问题int main(){// 循环引用kb::shared_ptr<Node> sp1(new Node);kb::shared_ptr<Node> sp2(new Node);cout << sp1.UseCount() << endl;cout << sp2.UseCount() << endl;sp1->_next = sp2;sp2->_prev = sp1;cout << sp1.UseCount() << endl;cout << sp2.UseCount() << endl;return 0;}

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

  1. C++ 98 中产生了第一个智能指针auto_ptr.
  2. C++ boost给出了更实用的scoped_ptr和shared_ptr和weak_ptr.
  3. C++ TR1,引入了shared_ptr等。不过注意的是TR1并不是标准版。
  4. C++ 11,引入了unique_ptr和shared_ptr和weak_ptr。需要注意的是unique_ptr对应boost的scoped_ptr。并且这些智能指针的实现原理是参考boost中的实现的。

五、总结

通过以上的内容我们知道智能指针其实也就是通过一个类来封装管理一个指针,在构造函数的地方把这个指针给智能指针的类管理,在智能指针的析构函数中释放内存;这样在程序结束的时候智能指针就会自动调用析构函数释放内存,所以正确地使用智能指针是可以有效地减少因忘记释放而导致内存泄漏的问题的。

5.1 智能指针一般都要处理以下问题:

一、RAII,资源申请即初始化,在对象构造时获取资源,接着控制对资源的访问使之在对象的生命周期内始终保持有效,最后在对象析构的时候释放资源。借此,我们实际上把管理一份资源的责任托管给了一个对象。这种做法有两大好处:
1、不需要显式地释放资源。
2、采用这种方式,对象所需的资源在其生命期内始终保持有效。

二、能够像指针一样使用。即这个智能指针要重载*,->,+等等指针能用的运算符。

三、拷贝问题。详情看上文介绍。

5.2 对比四个智能指针

1、auto_ptr是通过管理权转移的方式实现的,缺点就是会使被拷贝对象的指针悬空,再访问可能会导致程序崩溃。所以一般不建议使用auto_ptr。
2、unique_ptr是防拷贝的智能指针。禁止拷贝,简单粗暴,日常的不需要拷贝的场景建议使用。
3、shared_ptr是能够共享的指针,也就是说可以拷贝。通过引用计数来实现,但是可能会导致循环引用,循环引用会导致内存泄漏,这时需要用weak_ptr来配合使用解决循环引用问题。
4、weak_ptr不是RAII的智能指针,专门用来解决shared_ptr的循环引用问题,不参与引用计数的修改和指针的管理,能正常访问资源。

以上就是今天想要跟大家分享的所有内容了,你学会了吗?如果感觉到有所帮助,那么点点赞点点关注呗,后期还会持续更新C++的相关知识哦,我们下期见!!!!

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

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

相关文章

Kotlin vs Java:为什么Springboot官方教程选择了Kotlin?

导语 作为Java开发者的你&#xff0c;是否在为寻找Java的替代品而烦恼&#xff1f;担心受知识产权问题困扰&#xff1f;别担心&#xff0c;Kotlin来了&#xff01;它是你的救星&#xff0c;也是Springboot官网教程的选择。想知道为什么吗&#xff1f;那就往下翻吧&#xff01;…

NeurIPS 2023 | MQ-Det: 首个支持多模态查询的开放世界目标检测大模型

目前的开放世界目标检测模型大多遵循文本查询的模式&#xff0c;即利用类别文本描述在目标图像中查询潜在目标。然而&#xff0c;这种方式往往会面临“广而不精”的问题。一图胜千言&#xff0c;为此&#xff0c;作者提出了基于多模态查询的目标检测&#xff08;MQ-Det&#xf…

傅里叶变换和其图像处理中的应用

以下部分文字资料整合于网络&#xff0c;本文仅供自己学习用&#xff01; 一、为什么要在频域进行图像处理&#xff1f; 一些在空间域表述困难的增强任务&#xff0c;在频率域中变得非常普通 滤波在频率域更为直观&#xff0c;你想想嘛&#xff0c;所谓滤波&#xff0c;就是…

KOSMOS-2.5:密集文本的多模态读写模型

Overview 总览摘要1 引言2 KOSMOS-2.52.1 模型结构2.1 图像和文本表征2.3 预训练数据2.4 数据处理2.5 过滤与质量控制 3 实验3.1 评估3.2 实现细节3.3 结果3.4 讨论 4 相关工作4.1 多模态大语言模型4.2 图文理解 5 总结与展望 总览 题目: KOSMOS-2.5: A Multimodal Literate M…

通过jsoup抓取谷歌商店评分

文章目录 背景实现是否下架预警评分 总的工具类,测试 背景 在谷歌上面发布包,有时候要看看评分,有时候会因为总总原因被下架,希望后台能够对评分进行预警,和下架预警 实现 测试地址: https://play.google.com/store/apps/details?idcom.tencent.mm 通过jsoup解析页面,然后获…

Python学习----Day07

函数 函数是组织好的&#xff0c;可重复使用的&#xff0c;用来实现单一&#xff0c;或相关联功能的代码段。函数能提高应用的模块性&#xff0c;和代码的重复利用率。你已经知道Python提供了许多内建函数&#xff0c;比如print()。但你也可以自己创建函数&#xff0c;这被叫做…

苍穹外卖(五) 微信小程序

项目应用: 使用微信小程序完成客户端开发并基于微信登录实现小程序的登录功能如果是新用户需要自动完成注册 微信小程序开发 介绍 小程序是一种新的开放能力&#xff0c;开发者可以快速地开发一个小程序。可以在微信内被便捷地获取和传播&#xff0c;同时具有出色的使用体验…

C# 图解教程 第5版 —— 第3章 C# 编程概述

文章目录 3.1 一个简单的 C# 程序&#xff08;*&#xff09;3.2 标识符3.3 关键字3.4 Main&#xff1a;程序的起始点&#xff08;*&#xff09;3.5 空白3.6 语句&#xff08;*&#xff09;3.7 从程序中输出文本3.7.1 Write&#xff08;*&#xff09;3.7.2 WriteLine&#xff08…

【C++进阶】:C++类型转换

C类型转换 一.C语言里的类型转换二.C语音类型转换的一些弊端三.C的四种类型转换1.static_cast2.reinterpret_cast3.const_cast4.dynamic_cast 一.C语言里的类型转换 在C语言中&#xff0c;如果赋值运算符左右两侧类型不同&#xff0c;或者形参与实参类型不匹配&#xff0c;或者…

Python实验三

1&#xff1a;编程统计英文句子中的单词出现的次数。 要求&#xff1a;输出结果为按照单词在句子中出现的次数降序排列。 提示&#xff1a;用split&#xff08;&#xff09;拆分字符串 # 1&#xff1a;编程统计英文句子中的单词出现的次数。 # 要求&#xff1a;输出结果为按照…

Dijkstra求最短路(图解)

你好&#xff0c;我是Hasity。 今天分享的内容&#xff1a;Dijkstra求最短路这个题目 Dijkstra求最短路I 题目描述 给定一个 n个点 m 条边的有向图&#xff0c;图中可能存在重边和自环&#xff0c;所有边权均为正值。 请你求出 1 号点到 n号点的最短距离&#xff0c;如果无…

Windows 中环境变量的查看与设置

接触了LLM应用开发后&#xff0c;经常要用到环境变量的设置&#xff08;openAI apikey啥的&#xff09; 但是老忘记&#xff0c;今天来学习和总结一下 主要用到以下几种&#xff1a;使用 PowerShell、CMD 和 Python 来查看和设置环境变量 文章目录 1. PowerShell查看环境变量&a…

【Linux】HTTP协议

文章目录 &#x1f4d6; 前言1. 认识URL && 引入http协议2. http协议格式2.1 宏观格式&#xff1a;2.2 实验演示&#xff1a; 3. http的方法3.1 GET方法&#xff1a;3.2 POST方法&#xff1a;3.3 GET vs POST&#xff1a; 4. HTTP的报头和状态码5. http的cookie5.1 htt…

估算总体标准差的极差均值估计法sigma = R/d2

总体标准差的估算值可以通过将平均极差除以合适的常数因子d2来计算。这个估算方法是用于估算总体标准差的一种常见方法&#xff0c;尤其在质量控制和过程监控中经常使用。 总体标准差的估算值 (平均极差) / d2 其中&#xff1a; "总体标准差的估算值" 表示用极差…

Floorplanning with Graph Attention

Floorplanning with Graph Attention DAC ’22 目录 Floorplanning with Graph Attention摘要1.简介2.相关工作3.问题公式化4. FLORA的方法4.1 解决方案概述4.2 C-谱聚类算法 4.3 基于GAT的模型4.4 合成训练数据集生成 摘要 布图规划一直是一个关键的物理设计任务&#xff0…

宝塔面板部署express以及MySql项目

第一次在宝塔面板上部署express和MySql项目&#xff0c;部署过程一直跑不通接口&#xff0c;特此记录一下。 在部署的时候&#xff0c;建议第一步把数据库MySql给跑通&#xff0c;中间好多原因是由于数据库的原因给引起的。 一.连接数据库 &#xff08;1&#xff09;在宝塔面…

深入promise

深入promise 我们可能知道如何使用 Promise&#xff0c;但是我们知道它们实际上是如何工作的吗&#xff1f; 为了让每个人都了解Promise&#xff0c;让我们从基础开始。如果我们知道 Promise 是什么以及如何使用它&#xff0c;我们可以跳过这一部分并直接跳到“魔法开始”的地…

RSA加密与解密原理

目录 一、什么是RSA加密 二、RSA加密原理 三、RSA加解密过程与算法代码 一、什么是RSA加密 RSA加密是一种非对称加密算法。 对称加密&#xff1a; 对称加密是一种加密方式&#xff0c;加密和解密使用同一个密钥&#xff0c;被加密的信息在传输前用预先协商好的密钥进行加密…

KUKA机器人如何强制输出或取消数字IO信号?

KUKA机器人如何强制输出或取消数字IO信号? 具体的操作方法和步骤可参考以下内容: 如下图所示,点击菜单—显示—输入/输出端,如下图所示,选择想要查看的信号,这里以数字输出端为例进行说明, 如下图所示,此时可以看到输出端信号的编号、名称和当前值,可以通过下拉滚动条…

河北专升本(C语言)

目录 一&#xff1a;C语言的构成特点 二: 数据类型 三: 常量、变量、运算符及表达式 &#xff08;一&#xff09;标识符 &#xff08;二&#xff09;常量 &#xff08;三&#xff09;变量&#xff1a;其值可以改变的量 &#xff08;四&#xff09;各种类型数据混合运算 &…