C++如何正确使用智能指针?看完这4个点你就明白了_智能指针需要&

img
img

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以添加戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

}


在正常流程下,我们会在函数末尾 delete 创建的对象 w,正常调用析构函数,释放内存。


但是如果 w->do\_something()发生了异常,那么delete w将不会被执行。此时就会发生内存泄漏。我们当然可以使用 try…catch 捕捉异常,在 catch 里面执行 delete,但是这样代码上并不美观,也容易漏写。


如果我们用 std::unique\_ptr,那么这个问题就迎刃而解了。无论代码怎么抛异常,在 unique\_ptr 离开函数作用域的时候,内存就将会自动释放。


**shared\_ptr:共享所有权**  
 在使用 shared\_ptr 之前应该考虑,是否真的需要使用 shared\_ptr, 而非 unique\_ptr。


shared\_ptr 代表的是共享所有权,即多个 shared\_ptr 可以共享同一块内存。因此,从语义上来看,shared\_ptr 是支持复制的。如下:



auto w = std::make_shared();
{
auto w2 = w;
cout << w.use_count() << endl; // 2
}
cout << w.use_count() << endl; // 1


shared\_ptr 内部是利用引用计数来实现内存的自动管理,每当复制一个 shared\_ptr,引用计数会+1。当一个 shared\_ptr 离开作用域时,引用计数会-1。当引用计数为 0 的时候,则 delete 内存。


同时,shared\_ptr 也支持移动。从语义上来看,移动指的是所有权的传递。如下:



auto w = std::make_shared();
auto w2 = std::move(w); // 此时w等于nullptr,w2.use_count()等于1


我们将 w 对象 move 给 w2,意味着 w 放弃了对内存的所有权和管理,此时 w 对象等于 nullptr。而 w2 获得了对象所有权,但因为此时 w 已不再持有对象,因此 w2 的引用计数为 1。


**shared\_ptr性能**


内存占用高。 shared\_ptr 的内存占用是裸指针的两倍。因为除了要管理一个裸指针外,还要维护一个引用计数。因此相比于 unique\_ptr, shared\_ptr 的内存占用更高  
 原子操作性能低。 考虑到线程安全问题,引用计数的增减必须是原子操作。而原子操作一般情况下都比非原子操作慢。  
 使用移动优化性能。shared\_ptr 在性能上固然是低于 unique\_ptr。而通常情况,我们也可以尽量避免 shared\_ptr 复制。如果,一个 shared\_ptr 需要将所有权共享给另外一个新的 shared\_ptr,而我们确定在之后的代码中都不再使用这个 shared\_ptr,那么这是一个非常鲜明的移动语义。对于此种场景,我们尽量使用 std::move,将 shared\_ptr 转移给新的对象。因为移动不用增加引用计数,因此性能比复制更好。  
 使用场景


1. shared\_ptr 通常使用在共享权不明的场景。有可能多个对象同时管理同一个内存时。
2. 对象的延迟销毁。陈硕在《Linux 多线程服务器端编程》中提到,当一个对象的析构非常耗时,甚至影响到了关键线程的速度。可以使用BlockingQueue<std::shared\_ptr>将对象转移到另外一个线程中释放,从而解放关键线程。


**shared\_from\_this**


我们往往会需要在类内部使用自身的 shared\_ptr,例如:



class Widget
{
public:
void do_something(A& a)
{
a.widget = 该对象的shared_ptr;
}
}


我们需要把当前 shared\_ptr 对象同时交由对象 a 进行管理。意味着,当前对象的生命周期的结束不能早于对象 a。因为对象 a 在析构之前还是有可能会使用到a.widget。


如果我们直接a.widget = this;, 那肯定不行, 因为这样并没有增加当前 shared\_ptr 的引用计数。shared\_ptr 还是有可能早于对象 a 释放。


如果我们使用`a.widget = std::make_shared<Widget>(this);`,肯定也不行,因为这个新创建的 shared\_ptr,跟当前对象的 shared\_ptr 毫无关系。当前对象的 shared\_ptr 生命周期结束后,依然会释放掉当前内存,那么之后a.widget依然是不合法的。


对于这种,需要在对象内部获取该对象自身的 shared\_ptr, 那么该类必须继承std::enable\_shared\_from\_this。代码如下:



class Widget : public std::enable_shared_from_this
{
public:
void do_something(A& a)
{
a.widget = shared_from_this();
}
}


这样才是合法的做法。  
 **weak\_ptr**  
 weak\_ptr 是为了解决 shared\_ptr 双向引用的问题。即:



class B;
struct A{
shared_ptr b;
};
struct B{
shared_ptr a;
};
auto pa = make_shared();
auto pb = make_shared();
pa->b = pb;
pb->a = pa;


pa 和 pb 存在着循环引用,根据 shared\_ptr 引用计数的原理,pa 和 pb 都无法被正常的释放。对于这种情况, 我们可以使用 weak\_ptr:



class B;
struct A{
shared_ptr b;
};
struct B{
weak_ptr a;
};
auto pa = make_shared();
auto pb = make_shared();
pa->b = pb;
pb->a = pa;


weak\_ptr 不会增加引用计数,因此可以打破 shared\_ptr 的循环引用。通常做法是 parent 类持有 child 的 shared\_ptr, child 持有指向 parent 的 weak\_ptr。这样也更符合语义。


**如何指针作为函数传参**  
 很多时候,函数的参数是个指针。这个时候就会面临选择困难症,这个参数应该怎么传,应该是 shared\_ptr,还是 const shared\_ptr&,还是直接 raw pointer 更合适。


1. 只在函数使用指针,但并不保存。假如我们只需要在函数中,用这个对象处理一些事情,但不打算涉及其生命周期的管理,不打算通过函数传参延长 shared\_ptr 的生命周期。对于这种情况,可以使用 raw pointer 或者 const shared\_ptr&。即:



void func(Widget*);
void func(const shared_ptr&)


实际上第一种裸指针的方式可能更好,从语义上更加清楚,函数也不用关心智能指针的类型。


在函数中保存智能指针。假如我们需要在函数中把这个智能指针保存起来,这个时候建议直接传值。  
 void func(std::shared\_ptr ptr);  
 这样的话,外部传过来值的时候,可以选择 move 或者赋值。函数内部直接把这个对象通过 move 的方式保存起来。这样性能更好,而且外部调用也有多种选择。


**总结**  


![img](https://img-blog.csdnimg.cn/img_convert/0e396f1d0c0d1431c2f21e630a4dfe2a.png)
![img](https://img-blog.csdnimg.cn/img_convert/4c3910c780bedbc4dc3ed8ffac10e0ed.png)

**网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。**

**[需要这份系统化的资料的朋友,可以添加戳这里获取](https://bbs.youkuaiyun.com/topics/618668825)**


**一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!**

做到真正的技术提升。**

**[需要这份系统化的资料的朋友,可以添加戳这里获取](https://bbs.youkuaiyun.com/topics/618668825)**


**一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!**

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值