肯尼亚网站域名,东莞做网站设计制作,深圳网站制作公司兴田德润官方网站,网站有时打不开一、智能指针存在的意义
智能指针主要解决以下问题#xff1a;
#xff08;1#xff09;内存泄漏#xff1a;内存手动释放#xff0c;使用智能指针可以自动释放。
#xff08;2#xff09;共享所有权指针的传播和释放#xff0c;比如多线程使用同一个对象时析构问题…一、智能指针存在的意义
智能指针主要解决以下问题
1内存泄漏内存手动释放使用智能指针可以自动释放。
2共享所有权指针的传播和释放比如多线程使用同一个对象时析构问题。 C里面有四个智能指针auto_ptr、share_ptr、unique_ptr、weak_ptr。其中后三个是C11支持的并且第一个已经在C11弃用所以重点讲解share_ptr、unique_ptr、weak_ptr。
它们的特点
1unique_ptr独占对象的所有权由于没有引用计数性能较好。
2share_ptr共享对象的所有权但性能略差。
3weak_ptr配合share_ptr解决循环引用问题。 二、shared_ptr
std::shared_ptr使用引用计数每一个shared_ptr的拷贝都指向相同的内存。在最后一个shared_ptr析构的时候内存才会被释放。 sharedd_ptr共享被管理的对象同一时刻可以有多个shared_ptr拥有对象的所有权当最后一个shared_ptr对象销毁时被管理对象自动销毁。 简单的说shared_ptr实现包含了两个部分
1一个指向堆上创建的对象的裸指针 raw_ptr。
2一个指向内部隐藏的、共享的管理对象 shared_count_object。其中use_count是当前这个堆上对象被多少对象引用了简单来说就是引用计数。
2.1、shared_ptr内存模型 shared_ptr内部包含两个指针一个指向对象一个指向控制块。控制块包含一个引用计数、一个弱计数和其他数据比如删除器、分配器等。 其中reference count会累计对象的使用者数量。
std::shared_ptrint p1(new int(1));
std::shared_ptrint p2p1;上例中p1和p2的内存模型关系就是 2.2、shared_ptr使用场景
1使用智能指针可以自动释放占用的内存。
// Buffer对象分配在堆上但能自动释放
shared_ptrBuffer bufmake_sharedBuffer(auto free memory);// Buffer对象分配在堆上但需要手动delete释放
Buffer *buf2new Buffer(free memory);2共享所有权指针的传播和释放。 同样的数据但不同的业务处理不一样。使用shared_ptr智能指针可以减少内存拷贝因为有引入计数的存在当引入计数变为 0 时才真正去释放内存。
2.3、shared_ptr的基本使用和常用函数
1s.get()返回shared_ptr中保存的裸指针。
2s.reset(...)重置shared_ptr。
reset()不带参数时若智能指针s是唯一指向该对象的指针则释放并置空。若智能指针s不是唯一指向该对象的指针则引用计数减一同时将s置为空。reset()带参数时若智能指针s是唯一指向该对象的指针则释放并指向新的对象。若智能指针s不是唯一指向该对象的指针则引用计数减一并指向新的对象。 例如
auto smake_sharedint(100);
s.reset(new int(200));3s.use_count()返回shared_ptr的强引用计数。
4s.unique()若use_count为1返回true否则返回false。
2.3.1、初始化 make_shared / reset
通过构造函数、std::shared_ptr辅助函数和reset方法来初始化shared_ptr代码如下
std::shared_ptrint p1(new int(1));
std::shared_ptrint p2p1;
std::shared_ptrint p3;
p3.reset(new int(1));应该优先使用make_shared来构造智能指针因为它更高效。
auto p1make_sharedint(100);shared_ptrint p2make_sharedint(100);// 相当于
shared_ptrint p1(new int(100));不能将原始指针直接赋给一个智能指针。例如下面这种方法是错误的
std::shared_ptrint pnew int(1);shared_ptr不能通过“直接将原始这种赋值”来初始化需要通过构造函数和辅助方法来初始化。
对于一个未初始化的智能指针可以通过reset方法来初始化 当智能指针有值的时候调用reset会引起引用计数减1。 另外智能指针可以通过重载的bool类型操作符来判断。
#include iostream
#include memory
using namespace std;
int main()
{std::shared_ptrint p1;p1.reset(new int(1));std::shared_ptrint p2 p1;// 引用计数此时应该是2cout p2.use_count() p2.use_count() endl;p1.reset();cout p1.reset()\n;// 引用计数此时应该是1cout p2.use_count() p2.use_count() endl;if(!p1) {cout p1 is empty\n;}if(!p2) {cout p2 is empty\n;}p2.reset();cout p2.reset()\n;cout p2.use_count() p2.use_count() endl;if(!p2) {cout p2 is empty\n;}return 0;
}输出
p2.use_count() 2
p1.reset()
p2.use_count() 1
p1 is empty
p2.reset()
p2.use_count() 0
p2 is empty
2.3.2、获取原始指针 get()
当需要获取原始指针时可以通过get方法来返回原始指针代码如下所示 std::shared_ptrint ptr(new int(1));
int *p ptr.get();
// 不小心 delete p;谨慎使用get()的返回值如果不清楚其危险性则永远不要调用get()函数。
p.get()的返回值就相当于一个裸指针的值不合适的使用这个值上述陷阱的所有错误都有可能发生
遵守以下几个约定
不要保存get()的返回值 无论是保存为裸指针还是shared_ptr都是错误的。 保存为裸指针不知什么时候就会变成空悬指针保存为shared_ptr则产生了独立指针。 不要delete p.get()的返回值 会导致对一块内存delete两次的错误。 2.3.3、指定删除器
如果用shared_ptr管理非new对象或是没有析构函数的类时应当为其传递合适的删除器。
#include iostream
#include memoryusing namespace std;void DeleteIntPtr(int *p)
{cout Call DeleteIntPtrendl;delete p;
}int main(int argc, char **argv)
{shared_ptrint p(new int(1),DeleteIntPtr);return 0;
}当p的引用计数为0时自动调用删除器DeleteIntPtr来释放对象的内存。删除器可以是一个lambda表达式上面的写法可以改为
shared_ptrint p(new int(1),[](int *p){cout Call DeleteIntPtrendl;delete p;
});当使用shared_ptr管理动态数组时需要指定删除器因为shared_ptr的默认删除器不支持数据对象代码如下
std::shared_ptrint p3(new int[10],[](int *p){delete [] p;});2.4、shared_ptr使用要注意的问题
1不要用一个原始指针初始化多个shared_ptr。
错误示范如下
int *ptrnew int;
shared_ptrint p1(ptr);
shared_ptrint p2(ptr);//逻辑错误2不要在函数实参中创建shared_ptr。
错误示范如下
function(shared_ptrint(new int),g());因为C的函数参数的计算顺序在不同的编译器不同的约定下可能是不一样的一般是从右到左但也可能从左到右所以可能的过程是先new int然后调用g()如果恰好g()发生异常而shared_ptr还没有创建则int内存泄漏正确的写法应该是先创建智能指针代码如下
shared_ptrint p1(new int);
function(p1,g());3通过shared_from_this返回this指针。
不要将this指针作为shared_ptr返回回来因为this指针本质上是一个裸指针因此可能会导致重复析构如下例子 #include iostream
#include memoryusing namespace std;class MyClass
{
public:shared_ptrMyClass GetSelf() {return shared_ptrMyClass(this);//不要这样做}MyClass() {cout MyClass() endl;};~MyClass() {cout ~MyClass() endl;};};int main()
{shared_ptrMyClass sp1(new MyClass);shared_ptrMyClass sp2 sp1-GetSelf();return 0;
}运行后调用两次析构
MyClass()
~MyClass()
~MyClass()
free(): double free detected in tcache 2
已放弃 (核心已转储) 在这个例子中由于用同一个指针this)构造了两个智能指针sp1和sp2而他们之间是没有任何关系的在离开作用域之后this将会被构造的两个智能指针各自析构导致重复析构的错误。 正确返回this的shared_ptr的做法是让目标类继承std::enable_shared_from_this类然后使用基类的成员函数shared_from_this()返回this的shared_ptr如下所示
#include iostream
#include memoryusing namespace std;class MyClass: public std::enable_shared_from_thisMyClass
{
public:shared_ptrMyClass GetSelf() {return shared_from_this();//不要这样做}MyClass() {cout MyClass() endl;};~MyClass() {cout ~MyClass() endl;};};int main()
{shared_ptrMyClass sp1(new MyClass);shared_ptrMyClass sp2 sp1-GetSelf();return 0;
}执行结果
MyClass()
~MyClass() 4避免循环引用。
循环引用会导致内存泄漏。比如
#include iostream
#include memoryusing namespace std;class A;
class B;class B
{
public:shared_ptrA aptr;B();~B();private:};B::B()
{cout B() endl;
}B::~B()
{cout B is deleted endl;
}class A
{
public:shared_ptrB bptr;A();~A();private:};A::A()
{cout A() endl;
}A::~A()
{cout A is deleted endl;
}int main()
{shared_ptrA ap(new A);shared_ptrB bp(new B);ap-bptr bp;bp-aptr ap;cout main leave endl; // 循环引用导致ap bp退出了作用域都没有析构return 0;
}循环引用导致ap和bp的引用计数为2在离开作用域之后ap和bp的引用计数减为1并不回减为0导致两个指针都不会被析构产生内存泄漏。
解决的办法是把A和B任何一个成员变量改为weak_ptr。
三、unique_ptr
1unique_ptr是一个独占型的智能指针不能将其复制给另一个unique_ptr。
2unique_ptr可以指向一个数组。
3unique_ptr需要确定删除器的类型。
3.1、unique_ptr是一个独占型的智能指针
unique_ptr是一个独占型的智能指针它不允许其他的智能指针共享其内部的指针不允许通过赋值将一个unique_ptr赋值给另一个unique_ptr。下面的错误示例。
unique_ptrT my_ptr(new T);
unique_ptrT my_other_ptrmy_ptr;//报错不能复制3.2、std::move(...)转移unique_ptr
unique_ptr不允许复制但可以通过函数返回给其他的unique_ptr还可以通过std::move来转移到其他的unique_ptr这样它本身就不再拥有原来指针的所有权了。例如 unique_ptrT my_ptr(new T);
unique_ptrT my_ptr2std::move(my_ptr);
unique_ptrT my_ptr3my_ptr2;//报错不能复制3.3、make_unique初始化
std::make_shared是c11的一部分但std::make_unique不是。它是在c14里加入标准库的。
auto upw1(std::make_uniqueT());// with make func
std::unique_ptrT upw2(new T); // without make func使用new的版本重复了被创建对象的键入但是make_unique函数则没有。重复类型违背了软件工程的一个重要原则应该避免代码重复代码中的重复会引起编译次数增加导致目标代码膨胀。
3.4、unique_ptr与shared_ptr的区别
1unique_ptr可以指向一个数组。
std::unique_ptrint [] ptr(new int[10]);
ptr[9]9;
std::shared_ptrint [] ptr2(new int[10]);//这是不合法的2unique_ptr指定删除器和shared_ptr有区别。
std::shared_ptrint ptr3(new int(1), [](int *p){delete p;}); // 正确
std::unique_ptrint ptr4(new int(1), [](int *p){delete p;}); // 错误unique_ptr需要确定删除器的类型所以不能像shared_ptr那样直接指定删除器可以这样写
std::unique_ptrint,void(*)(int *) ptr(new int(1),[](int *p){delete p;});3.5、智能指针的选择
关于shared_ptr和unique_ptr的使用场景是要根据实际应用需求来选择。
如果希望只有一个智能指针管理资源或者管理数组就用unique_ptr如果希望多个智能指针管理同一个资源就用shared_ptr。 四、weak_ptr
share_ptr虽然已经很好用了但是有一点share_ptr智能指针还是有内存泄露的情况当两个对象相互使用一个shared_ptr成员变量指向对方会造成循环引用使引用计数失效从而导致内存泄漏。 weak_ptr 是一种不控制对象生命周期的智能指针, 它指向一个 shared_ptr 管理的对象. 进行该对象的内存管理的是那个强引用的shared_ptr weak_ptr只是提供了对管理对象的一个访问手段。 weak_ptr 设计的目的是为配合 shared_ptr 而引入的一种智能指针来协助 shared_ptr 工作, 它只可以从一个 shared_ptr 或另一个 weak_ptr 对象构造, 它的构造和析构不会引起引用记数的增加或减少。
4.1、weak_ptr的基本用法
1通过use_count()方法获取当前观察资源的引用计数。
shared_ptrint sp(new int(10));
weak_ptrint wp(sp);
// 或者
weak_ptrint wp2
wp2sp;cout wp.use_count() endl; //结果讲输出12通过expired()方法判断所观察资源是否已经释放。
shared_ptrint sp(new int(10));
weak_ptrint wp(sp);if(wp.expired())cout weak_ptr无效,资源已释放;
elsecout weak_ptr有效;3 通过lock方法获取监视的shared_ptr。
在多线程中要防止一个线程在使用智能指针而另一个线程删除指针指针问题可以使用weak_ptr的lock()方法。
std::weak_ptrint gw;
void f() { auto spt gw.lock(); if(gw.expired()) { cout gw无效,资源已释放; }else {cout gw有效, *spt *spt endl; }
}int main()
{ { auto sp std::make_sharedint(42); gw sp; f(); }f(); return 0;
}4.2、weak_ptr返回this指针
shared_ptr中提到不能直接将this指针返回shared_ptr需要通过派生std::enable_shared_from_this类并通过其方法shared_from_this来返回指针原因是std::enable_shared_from_this类中有一个weak_ptr这个weak_ptr用来观察this智能指针调用shared_from_this()方法是会调用内部这个weak_ptr的lock()方法将所观察的shared_ptr返回。
再看前面的范例
std::weak_ptrint gw;
void f() { auto spt gw.lock(); if(gw.expired()) { cout gw无效,资源已释放; }else {cout gw有效, *spt *spt endl; }
}int main()
{ { auto sp std::make_sharedint(42); gw sp; f(); }f(); return 0;
}在外面创建MyClass对象的智能指针和通过对象返回this的智能指针都是安全的因为shared_from_this()是内部的weak_ptr调用lock()方法之后返回的智能指针在离开作用域之后sp2的引用计数减为0A对象会被析构不会出现A对象被析构两次的问题。 需要注意的是获取自身智能指针的函数仅在shared_ptr的构造函数被调用之后才能使用因为enable_shared_from_this内部的weak_ptr只有通过shared_ptr才能构造。 4.3、weak_ptr解决循环引用问题
在shared_ptr提到智能指针循环引用的问题因为智能指针的循环引用会导致内存泄漏可以通过weak_ptr解决该问题只要将A或B的任意一个成员变量改为weak_ptr。
#include iostream
#include memoryusing namespace std;class A;
class B;class B
{
public:shared_ptrA aptr;B();~B();private:};B::B()
{cout B() endl;
}B::~B()
{cout B is deleted endl;
}class A
{
public:weak_ptrB bptr;// 修改为weak_ptrA();~A();private:};A::A()
{cout A() endl;
}A::~A()
{cout A is deleted endl;
}int main()
{shared_ptrA ap(new A);shared_ptrB bp(new B);ap-bptr bp;bp-aptr ap;cout main leave endl; // 循环引用导致ap bp退出了作用域都没有析构return 0;
}这样在对B的成员赋值时即执行bp-aptrap;时由于aptr是weak_ptr它并不会增加引用计数所以ap的引用计数仍然会是1在离开作用域之后ap的引用计数为减为0A指针会被析构析构后其内部的bptr的引用计数会被减为1然后在离开作用域后bp引用计数又从1减为0B对象也被析构不会发生内存泄漏。
4.4、weak_ptr使用注意事项
weak_ptr在使用前需要检查合法性。
weak_ptrint wp;
{shared_ptrint sp(new int(1));wpsp;shared_ptrint sp_ok wp.lock();//wp没有重载-操作符。只能这样取所指向的对象
}shared_ptrint sp_nullwp.lock();因为上述代码中sp和sp_ok离开了作用域其容纳的K对象已经被释放了。得到了一个容纳NULL指针的sp_null对象。在使用wp前需要调用wp.expired()函数判断一下。
因为wp还仍旧存在虽然引用计数等于0仍有某处“全局”性的存储块保存着这个计数信息。直到最后一个weak_ptr对象被析构这块“堆”存储块才能被回收。否则weak_ptr无法指到自己所容纳的那个指针资源的当前状态。 如果shared_ptrint sp_ok和weak_ptrint wp属于同一个作用域
weak_ptrint wp;
shared_ptrint sp_ok;
{shared_ptrint sp(new int(1));wp sp;sp_ok wp.lock();//wp没有重载-操作符。只能这样取所指向的对象
}if (wp.expired())cout shared_ptr is destroy endl;
elsecout shared_ptr not destroy endl;因为sp_ok还没析构use_count1。
五、智能指针安全性问题
引用计数本身是安全的至于智能指针是否安全需要结合实际使用分情况讨论。
1多线程代码操作的是同一个shared_ptr的对象此时是不安全的。
比如std::thread的回调函数是一个lambda表达式其中引用捕获一个shared_ptr
std::thread td([sp1](){...});又或者通过回调函数的参数传入的shared_ptr对象参数类型引用
void fun(shared_ptrA sp)
{
// ...
}
// ...std::thread td(fun,sp1);这个时候必然不是线程安全的。
2多线程代码操作的不是同一个shared_ptr的对象。
这里指的是管理的数据是同一份而shared_ptr不是同一个对象。** 比如多线程回调的lambda的是按值捕获的对象。
std::thread([sp1](){...});另个线程传递的shared_ptr是值传递而非引用
void fn(shared_ptrAsp) {
...
}
..
std::thread td(fn, sp1);这时候每个线程内看到的sp他们所管理的是同一份数据用的是同一个引用计数。但是各自是不同的对象当发生多线程中修改sp指向的操作的时候是不会出现非预期的异常行为的。
也就是说如下操作是安全的
void fun(shared_ptrA sp)
{
// ...if(...){spother_sp;}else {spother_sp2;}
}需要注意所管理数据的线程安全性问题。显而易见所管理的对象必然不是线程安全的必然 sp1、sp2、sp3智能指针实际都是指向对象A 三个线程同时操作对象A那对象的数据安全必然是需要对象A自己去保证。
总结
智能指针之间不能混用。
weak_ptr要和shared_ptr搭配使用不能单独使用weak_ptr。
weak_ptr的lock()使用是要先调用lock()再调用expired()。调用lock()之后use_count1。