C++之智能指针

为什么会有智能指针

前面我们知道使用异常可能会导致部分资源没有被正常释放, 因为异常抛出之后会直接跳转到捕获异常的地方从而跳过了一些很重要的的代码, 比如说下面的情况:

int div()
{int a, b;cin >> a >> b;if (b == 0)throw invalid_argument("除0错误");return a / b;
}
void Func()
{int* p1 = new int;int* p2 = new int;cout << div() << endl;cout << "delete p1" << endl;delete p1;cout << "delete p2" << endl;delete p2;
}
int main()
{try{Func();}catch (exception& e){cout << e.what() << endl;}return 0;
}

main函数中调用了func函数, func函数里面调用了div函数, func函数中没有捕捉异常, 但是在main函数里面却捕捉了异常, 所以出现异常的话就会导致func函数中的部分代码没有被执行, 进而导致内存泄漏:

无异常抛出正常内存释放: 

有异常抛出, 内存泄漏:  

为了解决这个问题就可以异常重新抛出, 在func函数里面添加捕获异常的代码, 然后在catch里面对资源进行释放最后重新将异常进行抛出, 最后交给main函数中的catch进行处理, 比如说下面的代码: 

int div()
{int a, b;cin >> a >> b;if (b == 0)throw invalid_argument("除0错误");return a / b;
}
void Func()
{int* p1 = new int;int* p2 = new int;try {cout << div() << endl;}catch (...){cout << "delete p1" << endl;delete p1;cout << "delete p2" << endl;delete p2;throw;}cout << "delete p1" << endl;delete p1;cout << "delete p2" << endl;delete p2;
}
int main()
{try { Func(); }catch (exception& e){cout << e.what() << endl;}return 0;
}

无论除零是否有异常都会正常释放资源: 

但是这么写就完成正确了吗? 肯定没有, 因为new本身也是会抛异常的, 当内存不足却又使用new申请空间的话就会导致开辟空间失败从而抛出异常, 那new抛异常会导致什么结果呢?

首先p1抛出异常会有什么问题吗?

没有, p1抛出异常会直接跳转到main函数里面进行捕捉并且p2还没有开辟, p1没有开辟成功, 从而不会导致任何的内存泄漏.

那要是p2开辟失败了呢?

这时候也是会直接跳转到main函数里面进行捕捉, 但是p1已经开辟空间了, 如果p2开辟空间失败他会导致p1的申请的资源没有被正常释放, 所以为了安全起见我们给p2也添加一个try上去并且try块里面还得含有后面的代码, 因为一旦内存申请失败后面的调用函数也无需执行了.

int div()
{int a, b;cin >> a >> b;if (b == 0)throw invalid_argument("除0错误");return a / b;
}
void Func()
{int* p1 = new int;try{int* p2 = new int;try{cout << div() << endl;}catch (...){cout << "delete p1" << endl;delete p1;cout << "delete p2" << endl;delete p2;throw;}cout << "delete p1" << endl;delete p1;cout << "delete p2" << endl;delete p2;}catch (...){cout << "delete p1" << endl;delete p1;throw;}
}
int main()
{try { Func(); }catch (exception& e){cout << e.what() << endl;}return 0;
}

我们这里只new了两个对象, 那如果有三个有四个甚至更多的话又该如何来嵌套try catch呢? 所以当出现连续抛出异常的情况时, 我们之前学习的try catch语句就很难进行应对, 那么为了解决这个问题就有了智能指针


智能指针的使用及原理

RAII

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

注意: 智能指针是RAII的一种实现方式.


智能指针实现 

首先智能指针是一个类, 并且这个类要处理各种各样的数据, 所以这个类就要是模板类, 比如:

#pragma once
template<class T>
class SmartPoint
{
public:// RAIISmartPoint(T* ptr = nullptr):_ptr(ptr){}~SmartPoint(){cout << "~SmartPoint" << endl;delete _ptr;}
private:T* _ptr;
};

构造函数拿T类型的指针来初始化内部的_ptr就行, 析构函数在内部使用delete释放指针指向的空间即可.

#include<iostream>
using namespace std;
#include"SmartPoint.h"int div()
{int a, b;cin >> a >> b;if (b == 0)throw invalid_argument("除0错误");return a / b;
}void Func()
{SmartPoint<int> sp1 = new int;SmartPoint<int> sp2 = new int;cout << div() << endl;//有了智能指针就不需要显示释放资源//cout << "delete p1" << endl;//delete p1;//cout << "delete p2" << endl;//delete p2;
}int main()
{try { Func(); }catch (exception& e){cout << e.what() << endl;}return 0;
}

可以看到出现了除0错误这里也可以将两个申请的空间进行释放, 原理就是智能指针对象的生命周期属于Func函数, 当除0错误抛出异常的时候会从当前函数递归式的匹配catch直到main函数, 在匹配catch的时候虽然函数不会向下执行,但是函数栈帧会随之销毁, 其中类对象的生命周期也就跟着结束, 就会自动调用析构函数来释放空间, 就解决了之前的问题. 而且这样做不需要显式地释放资源, 而且对象所需的资源在其生命期内始终保持有效.


智能指针的原理

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

template<class T>
class SmartPoint
{
public:// RAIISmartPoint(T* ptr = nullptr):_ptr(ptr){}~SmartPoint(){cout << "~SmartPoint" << endl;delete _ptr;}// 像指针一样T& oprator* (){return *_ptr;}T* operator->(){return _ptr;}
private:T* _ptr;
};
#include<iostream>
using namespace std;
#include"SmartPoint.h"struct Date
{int _year;int _month;int _day;
};int main()
{SmartPoint<int> sp1(new int);*sp1 = 10;cout << *sp1 << endl;SmartPoint<Date> sparray(new Date);// 需要注意的是这里应该是sparray.operator->()->_year = 2018;// 本来应该是sparray->->_year这里语法上为了可读性,省略了一个->sparray->_year = 2023;sparray->_month = 12;sparray->_day = 16;cout << "year:" << sparray->_year<< endl;cout << "month:" << sparray->_month << endl;cout << "day:" << sparray->_day << endl;return 0;
}

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

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


C++库中的智能指针

在这之前我们首先来看看下面这段代码:

void func2()
{SmartPoint<int>sp1(new int(10));SmartPoint<int>sp2(sp1);
}int main()
{func2();return 0;
}

原因很简单我们自己实现的类里面没有拷贝构造函数, 所以默认的拷贝构造函数以浅拷贝的方式进行构造, 所以对象的生命周期结束时就会调用delete将同一份空间析构两次, 所以就会报错.

那么为了解决这个问题我们就得自己来实现一个拷贝构造函数, 可是这里的拷贝构造并不能是深拷贝, 因为我们这个类的目的是让它和指针一样对资源进行管理, 而并不是一个容器对资源进行存储. 所以这里就不能采用深拷贝的形式来进行拷贝构造, 那么库中是如何来解决这个问题的呢?

先看看最早的auto_ptr如何解决这个问题: 

std::auto_ptr

std::auto_ptr文档

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

简化模拟实现一份test::auto_ptr来了解它的原理 :

namespace test
{// C++98// 管理权转移,最后一个拷贝对象管理资源,被拷贝对象都被置空template<class T>class auto_ptr{public:// RAIIauto_ptr(T* ptr):_ptr(ptr){}~auto_ptr(){if (_ptr){cout << "delete->" << _ptr << endl;delete _ptr;_ptr = nullptr;}}// ap2(ap1)auto_ptr(auto_ptr<T>& ap):_ptr(ap._ptr){//管理权转移ap._ptr = nullptr;}auto_ptr<T>& operator=(auto_ptr<T>& ap){// 检测是否为自己给自己赋值if (this != &ap){// 释放当前对象中资源if (_ptr)delete _ptr;// 转移ap中资源到当前对象中_ptr = ap._ptr;ap._ptr = nullptr;}return *this;}// 像指针一样T& operator*(){return *_ptr;}T* operator->(){return _ptr;}private:T* _ptr;};}

auto_ptr的解决方法就是将管理权进行转移, 把原来的智能指针变为空, 让新的智能指针指向这个空间, 

int main()
{std::auto_ptr<int> sp1(new int);std::auto_ptr<int> sp2(sp1); // 管理权转移// sp1悬空*sp2 = 10;cout << *sp2 << endl;cout << *sp1 << endl;//解引用空指针return 0;
}

结论: auto_ptr是一个失败设计, 很多场景明确要求不能使用auto_ptr, 原因就是auto_ptr使用的方式太不合常理了.

后来为了解决auto_ptr难用的问题, 就有了unique_ptrshare_ptr/weak_ptr .


std::unique_ptr

unique_ptr文档

	// C++11 template<class T>class unique_ptr{public:// RAIIunique_ptr(T* ptr):_ptr(ptr){}~unique_ptr(){cout << "delete->" << _ptr << endl;delete _ptr;}// 像指针一样T& operator*(){return *_ptr;}T* operator->(){return _ptr;}// C++11unique_ptr(const unique_ptr<T>& up) = delete;unique_ptr<T>& operator=(const unique_ptr<T>& up) = delete;private:// C++98// 1、只声明不实现// 2、限定为私有//unique_ptr(const unique_ptr<T>& up);//unique_ptr<T>& operator=(const unique_ptr<T>& up);private:T* _ptr;};

unique_ptr解决拷贝构造问题的思路简单粗暴, 直接禁止拷贝构造(拷贝构造设为私有或者C++11中设为delete).

int main()
{test::unique_ptr<int> sp1(new int);test::unique_ptr<int> sp2(sp1);test::unique_ptr<int> sp3(new int);sp3 = sp1;return 0;
}


std::shared_ptr

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

std::shared_ptr文档

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

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

 一个智能指针指向两块空间, 一块用于存储数据, 另一块记录当前空间被几个智能指针所指向, 当前只有对象sp1指向这个空间所以当前的计数就为1:

当我们再创建一个对象sp2并指向这个空间时图片就变成了:

当sp1对象生命周期结束, 或者sp1指向其他内容时, 就变成了: 

当引用计数为0时, 空间被释放: 

int main()
{std::shared_ptr<string> sp1(new string("xxxxxxxxxx"));std::shared_ptr<string> sp2 = sp1;std::shared_ptr<string> sp3;sp3 = sp1;std::shared_ptr<string> sp4(new string("xxxxxxxxxxxxxxxxx"));sp3 = sp4;cout << *sp1 << endl;cout << *sp2 << endl;cout << *sp3 << endl;cout << *sp4 << endl;return 0;
}

可以看到使用share_ptr既不会出现拷贝完原指针置空(auto_ptr)的情况, 也不会出现禁止拷贝(unique_ptr)的情况, 而且这个智能指针跟普通的指针一样指向的是同一块区域的内容.

模拟实现一下shared_ptr:

首先有个问题: 引用计数的空间如何来分配?

可以是个普通的整型变量放到对象里面吗?

不可以, 因为当计数变量的值发生改变时, 所有指向该空间对象的内部计数变量都得改变, 此时只改变一个对象的引用计数对其它对象内存储的引用计数毫无影响.

那么可以使用静态成员变量来实现吗?

看上去可以, 因为不管类实例化出来了多少个对象, 这个静态变量只有一个, 并且所有对象都会共享这个静态变量, 那么这时只要一个对象对这个静态变量进行修改的话, 其他对象都会跟着一起修改, 这是不是达到了我们的目的呢?

其实没有, 因为静态变量虽然一个对象修改所有对象都会共享, 但是这个对象指的是这个类所有实例化出来的对象, 假如又有一个智能指针指向一块新开辟的资源需要管理, 那么这个智能指针的引用计数需要初始化为1, 这就影响了之前的引用计数, 虽然是同一个类实例化出来的对象, 但是管理的资源可能会不同, 引用计数混在一起就乱套了.

正确的方法是在类里面添加一个整型的指针变量, 让指针指向new开辟的一块空间:

template<class T>
class shared_ptr
{
public:// RAIIshared_ptr(T* ptr = nullptr):_ptr(ptr),_pcount(new int(1)){}~shared_ptr(){if (--(*_pcount) == 0){cout << "delete->" << _ptr << endl;delete _ptr;delete _pcount;}}// 像指针一样T& operator*(){return *_ptr;}T* operator->(){return _ptr;}private:T* _ptr;int* _pcount;
};

构造函数中将引用计数初始化为1, 调用一次析构函数引用计数就--一次, 当引用计数为0时就释放被管理的资源和为引用计数开辟的那块资源. 

拷贝构造函数:

// sp2(sp1)
shared_ptr(const shared_ptr<T>& sp):_ptr(sp._ptr),_pcount(sp._pcount)
{(*_pcount)++;
}

 拷贝构造函数比较简单, 把_ptr和_pcount拷贝过来然后引用计数++即可.

赋值重载就需要注意了: 

//sp2 = sp1
shared_ptr<T>& operator=(const shared_ptr<T>& sp)
{//要考虑是不是自己给自己赋值if (_ptr != sp._ptr){if (--*(_pcount) == 0){delete _ptr;delete _pcount;}_ptr = sp._ptr;_pcount = sp._pcount;(*_pcount)++;}return *this;
}

首先要判断要拷贝的被拷贝的对象指向的是不是同一块空间:

1. 如果是同一块空间(也就是自己给自己赋值)就没有必要进行拷贝, 或者说不能进行拷贝, 因为按照这个逻辑会先把引用计数--判断其是否为0决定原来的空间要不要释放, 如果当前引用计数不为1还好, 只是重复拷贝的问题, 但是如果引用计数为1, 先--引用计数然后把空间释放了, 再去拷贝这段已经被释放了的空间, 显然是不对的, 所以要先判断是否是自己给自己赋值.

自己给自己赋值不要用this != &sp来判断了, 因为这里的自己赋值实际指的是底层管理的那段空间是不是一个空间, 所以用_ptr != sp._ptr来判断.

2. 如果不是同一块空间, 先要把原来的引用计数--, 并判断是否为0, 为0就需要把原来的空间释放掉, 处理完原来的空间再去拷贝新的空间.


std::shared_ptr的循环引用

首先我们创建一个名为Listnode的类, 类里面含有两个listnode的指针和一个int的变量用来存储数据, 然后创建一个析构函数用来作为标记, 那么这里的代码就如下:

struct Listnode
{Listnode* next;Listnode* prev;int val;
};void test_shared_ptr()
{test::shared_ptr<Listnode> ptr1 = new Listnode;test::shared_ptr<Listnode> ptr2 = new Listnode;//ptr1->next = ptr2;//ptr2->prev = ptr1;
}

可以看到我希望用shared_ptr去管理创建的Listnode, 但是ptr1->next = ptr2 和ptr2->prev = ptr1的赋值肯定是不能兼容的, 所以改变一下Listnode的成员变量:

struct Listnode
{test::shared_ptr<Listnode> next;test::shared_ptr<Listnode> prev;int val;//顺便添加一个析构, 等会以便于观察~Listnode(){cout << "~Listnode()" << endl;}
};void test_shared_ptr()
{test::shared_ptr<Listnode> n1 = new Listnode;test::shared_ptr<Listnode> n2 = new Listnode;//循环引用n1->next = n2;n2->prev = n1;
}

调用 test_shared_ptr():

int main()
{test_shared_ptr();return 0;
}

 

发现没有调用析构, 也就是发生了内存泄漏. 

现在将下面一行代码注释掉, 看看是否能正常析构:

void test_shared_ptr()
{test::shared_ptr<Listnode> n1 = new Listnode;test::shared_ptr<Listnode> n2 = new Listnode;//循环引用n1->next = n2;//n2->prev = n1;
}int main()
{test_shared_ptr();return 0;
}

可以正常析构, 为什么呢? 

 这是一开始n1和n2都只是默认初始化时的状态:

 n1->next = n2之后, 指针指向变成了这样:

这种情况下n1和n2可以正常, 按照构造函数相反的方向, n2的析构函数先调用, 引用计数--为1, 不发生任何改变, n1的析构函数再调用, 引用计数--为0, 调用delete _ptr也就是调用Listnode的析构函数, 打印~ListNode()后, prev先析构, 正常析构即可, next析构引用计数--为0, 释放原来n2指向的那片Listnode空间, 这部分空间内的两个shared_ptr都未初始化, 正常析构没有问题, 至此析构完成.

再来重新分析这段代码的析构:

void test_shared_ptr()
{test::shared_ptr<Listnode> n1 = new Listnode;test::shared_ptr<Listnode> n2 = new Listnode;//循环引用n1->next = n2;n2->prev = n1;
}

同样的, n2先析构(*_pcount)--为1, n1再析构(*_pcount)--为1, 此时析构已经调用完成了, 引用计数都没有减到0, 不会对资源进行释放, 就发生了内存泄漏, 这也就是shared_ptr发生的循环引用问题.

对于std:: shared_ptr也同样有这个问题, 而且库里的shared_ptr的构造函数加了explicit, 不能隐式类型转换了:

struct Listnode
{int val;std::shared_ptr<Listnode> next;std::shared_ptr<Listnode> prev;~Listnode(){cout << "~Listnode()" << endl;}
};void test_shared_ptr2()
{std::shared_ptr<Listnode> n1(new Listnode);std::shared_ptr<Listnode> n2(new Listnode);//循环引用n1->next = n2;n2->prev = n1;
}int main()
{test_shared_ptr();return 0;
}


std::weak_ptr 

上面的问题最根本在于我们默许prev和next指针参与资源的管理, 因此为了解决循环引用的问题,weak_ptr就出现了. 库里面的解决方式是把Listnode里的next和prev换成weak_ptr :

 可以看到weak_ptr是支持用shared_ptr去构造的.

struct Listnode
{int val;std::weak_ptr<Listnode> next;std::weak_ptr<Listnode> prev;~Listnode(){cout << "~Listnode()" << endl;}
};

weak_ptr是一个有资源指向能力但没有管理权的指针, 所以无论创建多少个weak_ptr, 对应shared_ptr的引用计数也不会增加.

 weak_ptr特点

1. 不是传统的智能指针, 不支持RAII

2. weak_ptr中只有一个普通指针作为成员变量, 用来指向开辟的内存空间, 它不会去管理引用计数, 但是能去查看引用计数.

3. 能像指针一样使用

 虽然weak_ptr不能去管理引用计数, 但是它需要能查看引用计数, 因为假如有一个shared_ptr和weak_ptr指向同一块空间, shared_ptr释放了但是weak_ptr并不知道, 为了解决这个问题, weak_ptr也有use_count接口可以查看引用计数, 其次它还有一个expired接口检查它是否"过期".

void test_shared_ptr2()
{std::shared_ptr<Listnode> n1(new Listnode);std::shared_ptr<Listnode> n2(new Listnode);cout << n1.use_count()<<endl;cout << n2.use_count() << endl;//循环引用n1->next = n2;n2->prev = n1;//weak_ptr接口cout << n1->next.use_count() << endl;cout << n2->prev.use_count() << endl;cout << n1->next.expired() << endl;;cout << n2->prev.expired() << endl;;
}

 简单实现一个weak_ptr:

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>& wp){_ptr = wp.get();return *this;}//像指针一样使用T* operator->(){return _ptr;}T& operator*(){return *_ptr;}private:T* _ptr;
};

这里用shared_ptr去构造weak_ptr需要调用shared_ptr的get接口去得到管理的那部分空间的地址, 在shared_ptr里添加一个get:


定制删除器 

智能指针不光可以管理一个变量, 也可以管理一个数组。

对于一个变量而言,可以使用delete _ptr,但对于一个数组而言,回收资源就需要用delete[] _ptr,可是我们之前实现的shared_ptr释放用的是delete, 所以去管理一个数组空间时析构会崩溃(std::shared_ptr也会崩溃):

void test_shared_ptr3()
{test::shared_ptr<string> n(new string[10]);//std::shared_ptr<string> n(new string[10]);
}

所以这里我们需要一个定制删除器来实现对不同资源的不同释放方式 :

 在构造shared_ptr时可以传递第二个 对象参数, 也就是定制删除器, 来实现自定义的删除方式.

1. 我们可以选择传递一个仿函数对象: 

template<class T>
struct ListnodeDL
{void operator()(T* ptr){delete[] ptr;}
};
void test_shared_ptr3()
{std::shared_ptr<Listnode> p(new Listnode[10], ListnodeDL<Listnode>());
}int main()
{test_shared_ptr3();return 0;
}

2. 还可以选择传递一个lambda对象, 更加方便:

void test_shared_ptr3()
{std::shared_ptr<Listnode> p(new Listnode[10], [](Listnode* ptr) {delete[] ptr; });
}

 3. 还可以去管理一个文件指针:

void test_shared_ptr3()
{std::shared_ptr<FILE> p3(fopen("test.cpp", "r"), [](FILE* ptr) {fclose(ptr); });
}

 了解完定制删除器后, 现在可以为之前模拟实现的shared_ptr添加上删除器:

 

可以看到新添加了一个包装器成员_del, 因为我们要在释放资源时中去调用删除器自己实现的删除功能, 它是一个返回值类型为void参数为T*类型的函数包装器.

注意: 这里_del要给一个缺省值, 或者在单参数的构造里初始化列表初始化, 因为之前的delete注释掉了, 对于普通场景的资源释放也需要处理.

完整代码: 

	template<class T>class shared_ptr{public:// RAIIshared_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){}~shared_ptr(){if (--(*_pcount) == 0){//cout << "delete->" << _ptr << endl;//delete _ptr; 有了定制删除器, 就不能直接用delete了_del(_ptr);delete _pcount;}}// sp2(sp1)shared_ptr(const shared_ptr<T>& sp):_ptr(sp._ptr),_pcount(sp._pcount){(*_pcount)++;}//sp2 = sp1shared_ptr<T>& operator=(const shared_ptr<T>& sp){if (_ptr != sp._ptr){(*_pcount)--;if (*_pcount == 0){//delete _ptr;_del(_ptr);delete _pcount;}_ptr = sp._ptr;_pcount = sp._pcount;(*_pcount)++;}return *this;}T* get() const{return _ptr;}// 像指针一样T& operator*(){return *_ptr;}T* operator->(){return _ptr;}private:T* _ptr;int* _pcount;function<void(T*)>_del = [](T* ptr) {delete ptr; };};

测试一下: 

void test_shared_ptr3()
{test::shared_ptr<Listnode> p1(new Listnode[10], ListnodeDL<Listnode>());test::shared_ptr<Listnode> p2(new Listnode[10], [](Listnode* ptr) {delete[] ptr; });test::shared_ptr<FILE> p3(fopen("test.txt","r"), [](FILE* ptr) {fclose(ptr); });test::shared_ptr<Listnode> p4(new Listnode);
}int main()
{test_shared_ptr3();return 0;
}

传alloc参数 

此外, 库中还有一种构造可以传Allocator, 这是为了防止内存碎片化, 因为每一个智能指针都维护一个引用计数, 大量使用智能指针就会有大量的内存碎片


 C++智能指针发展历史

C++ 98的auto_ptr: 管理权的转移->不好的设计, 对象悬空. (不建议使用)

boost的scoped_ptr: 防止拷贝->简单粗暴, 对于不需要拷贝的场景很好.

boost的shared_ptr: 引用计数, 最后一个释放的对象释放资源-> 复杂一些, 但支持拷贝, 很好->问题, 循环引用.

C++11的unique_ptr: 防止拷贝->简单粗暴, 对于不需要拷贝的场景很好.

C++11的shared_ptr: 引用计数, 最后一个释放的对象释放资源-> 复杂一些, 但支持拷贝, 很好->问题, 循环引用.

而C++11的unique_ptr和shared_ptr, 分别对应boost库中的scoped_ptr和shared_ptr. 

什么是boost?

一项改变要正式进入标准是很严谨的事情, 所以标准委员会库工作组织成立了一个boost的第三方库作为标准库的后备:

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

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

相关文章

《程序员的职业迷宫:选择你的职业赛道》

程序员如何选择职业赛道&#xff1f; 大家好&#xff0c;我是小明&#xff0c;一名在编程迷宫中探索的程序员。作为这个庞大迷宫的探险者&#xff0c;我深知选择适合自己的职业赛道有多么重要。今天&#xff0c;我将分享一些关于如何选择职业赛道的心得&#xff0c;希望能够帮…

爬虫案例二

想拿到电影天堂 其中一个下载地址如何实现呢 第一步电影天堂_免费在线观看_迅雷电影下载_电影天堂网 (dytt28.com)电影天堂_电影下载_高清首发 (dytt89.com)电影天堂_免费在线观看_迅雷电影下载_电影天堂网 (dytt28.com) 第一步 我直接打开 requests.exceptions.SSLError: H…

C语言——结构体(位段)、联合体、枚举

hello&#xff0c;大家好&#xff01;我是柚子&#xff0c;今天给大家分享的内容是C语言中的自定义类型结构体、联合体以及枚举&#xff0c;有什么疑问或建议可以在评论区留言&#xff0c;会顺评论区回访哦~ 一、结构体 struct a.结构体声明 不同于数组的是&#xff0c;结构…

分布式ID生成算法|雪花算法 Snowflake | Go实现

写在前面 在分布式领域中&#xff0c;不可避免的需要生成一个全局唯一ID。而在近几年的发展中有许多分布式ID生成算法&#xff0c;比较经典的就是 Twitter 的雪花算法(Snowflake Algorithm)。当然国内也有美团的基于snowflake改进的Leaf算法。那么今天我们就来介绍一下雪花算法…

sylar高性能服务器-日志(P57-P60)内容记录

文章目录 P57-P60&#xff1a;序列化模块Varint&#xff08;编码&#xff09;Zigzag&#xff08;压缩&#xff09;class ByteArrayNode&#xff08;链表结构&#xff09;成员变量构造函数写入读取setPositionaddCapacity 测试 P57-P60&#xff1a;序列化模块 ​ 序列化模块通常…

单调栈的理解

单调栈的理解 核心代码场景思考 完整代码环形数组循环数组 单调栈&#xff1a; 单调递增或 单调递减的栈 核心代码 while (!s.empty()&&s.peek()<nums[i]){s.pop(); } s.push(nums[i]);将要放入的元素&#xff0c;与栈内元素依个比较&#xff0c;小于的都出栈&am…

设计模式——2_3 迭代器(Iterator)

生活就像一颗巧克力&#xff0c;你永远不知道下一颗是什么味道 ——《阿甘正传》 文章目录 定义图纸一个例子&#xff1a;假如你的供应商提供了不同类型的返回值单独的遍历流程实现 碎碎念如果读写同时进行会发生啥&#xff1f;外部迭代和内部迭代迭代器和其他模式迭代器和组合…

彻底解析:企业为何必须采用CRM系统以及其五大作用

相关数据显示&#xff0c;CRM系统在欧美发达国家的普及程度高&#xff0c;超出80%的企业部署了CRM管理系统。然而在国内这个比例依然很小只有10几%&#xff0c;为什么企业需要CRM系统&#xff1f;因为CRM可以为公司实现线索管理、绩效管理、销售流程管理、市场营销管理以及数据…

Python爬虫:设置随机 User-Agent

Python爬虫&#xff1a;设置随机 User-Agent 在Python中编写爬虫时&#xff0c;为了模拟真实用户的行为并防止被服务器识别为爬虫&#xff0c;通常需要设置随机的User-Agent。你可以使用fake-useragent库来实现这一功能。首先&#xff0c;你需要安装fake-useragent库&#xff…

C++进阶之路---继承(一)

顾得泉&#xff1a;个人主页 个人专栏&#xff1a;《Linux操作系统》 《C从入门到精通》 《LeedCode刷题》 键盘敲烂&#xff0c;年薪百万&#xff01; 一、继承的概念及定义 1.继承的概念 继承(inheritance)机制是面向对象程序设计使代码可以复用的最重要的手段&#xff0…

【爬虫】单首音乐的爬取(附源码)

以某狗音乐为例 import requests import re import time import hashlibdef GetResponse(url):# 模拟浏览器headers {User-Agent:Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36 Edg/122.0.0.0}# 发送请求…

第五十回 插翅虎枷打白秀英 美髯公误失小衙内-mayfly-go:web 版 linux、数据库等管理平台

晁盖宋江和吴用到山下迎接雷横上山&#xff0c;宋江邀请雷横入伙&#xff0c;雷横以母亲年事已高为由拒绝了。 雷横回到郓城&#xff0c;听李小二说从东京新来了个表演的叫白秀英&#xff0c;吹拉弹唱跳&#xff0c;样样精通&#xff0c;于是雷横和李小二一起到戏院去看演出。…

Spring Webflux 详解

目录 0、组件对比 1、WebFlux 1、引入 2、Reactor Core 1、HttpHandler、HttpServer 3、DispatcherHandler 1、请求处理流程 4、注解开发 1、目标方法传参 2.返回值写法 5、文件上传 6、错误处理 7、RequestContext 8、自定义Flux配置 9、Filter WebFlux&am…

Java消息服务(JMS):在异步通信世界的引领者

文章目录 前言需求演进异步通信的需求增长面向消息的中间件兴起标准化的迫切需求 与相似框架的对比JMS vs AMQP&#xff08;Advanced Message Queuing Protocol&#xff09;JMS vs MQTT&#xff08;Message Queuing Telemetry Transport&#xff09;JMS vs Apache Kafka 完整的…

nginx,php-fpm

一&#xff0c;Nginx是异步非阻塞多进程&#xff0c;io多路复用 1、master进程&#xff1a;管理进程 master进程主要用来管理worker进程&#xff0c;具体包括如下4个主要功能&#xff1a; &#xff08;1&#xff09;接收来自外界的信号。 &#xff08;2&#xff09;向各worker进…

腾讯云服务器99元一年购买入口链接

腾讯云服务器99元一年购买入口链接如下&#xff0c;现在已经降价到61元一年&#xff0c;官方活动链接如下&#xff1a; 腾讯云99元服务器一年购买页面腾讯云活动汇聚了腾讯云最新的促销打折、优惠折扣等信息&#xff0c;你在这里可以找到云服务器、域名、数据库、小程序等等多种…

OSPF NSSA实验简述

OSPF NSSA实验简述 1、OSPF NSSA区域配置 为解决末端区域维护过大LSDB带来的问题&#xff0c;通过配置stub 区域或totally stub区域可以解决&#xff0c;但是他们都不能引入外部路由场景。 No so stuby area &#xff08;区域&#xff09;NSSA 可以引入外部路由&#xff0c;支持…

LLM 系列——BERT——论文解读

一、概述 1、是什么 是单模态“小”语言模型&#xff0c;是一个“Bidirectional Encoder Representations fromTransformers”的缩写&#xff0c;是一个语言预训练模型&#xff0c;通过随机掩盖一些词&#xff0c;然后预测这些被遮盖的词来训练双向语言模型&#xff08;编码器…

消息队列实现AB进程对话

进程A代码&#xff1a; #include <stdio.h>#include <sys/types.h>#include <sys/ipc.h>#include <sys/msg.h>#include <stdlib.h>#include <string.h>#define MSG_EXCEPT 020000struct msgbuf{long mtype;char mtext[100];};int main(in…

3/5 work

1> 使用select实现tcp的服务器端&#xff0c;poll实现tcp的客户端&#xff08;君子作业&#xff09; 2> 将课堂上实现的模型重新自己实现一遍 #include<myhead.h> #define SER_IP "192.168.124.23" #define SER_PORT 8888 int main(int a…