cpp是一种没有垃圾回收的语言,给了程序员十足的控制感,同时也为内存泄漏问题留下了重大的隐患。Modern Cpp不推荐程序员完全用new和delete手动管理内存,而是应该使用智能指针。这一节我们就来梳理下智能指针的特性与应用场景。
auto_ptr
我们从std::auto_ptr
说起。在C++ 98/03中,C++引入了std::auto_ptr
帮助管理内存。随着cpp新标准的出现, std::auto_ptr
已被彻底废弃了。被废弃的原因是因为auto_ptr在operator=赋值和复制构造过程中具有的控制权自动转移特性。
1 |
|
如上,将p1赋值给p3将导致p1将所持有的堆内存转移给p3,之后p1的原始指针将等于Null。通过p2来构造p4也是相同结果。不完善的控制权转移特性被认为是auto_ptr的重大设计缺陷,使得其不被社区所接受。这种默认的控制权转移机制在Rust语言中得到了很好的发展,Rust语言提供了完善的机制来管理变量的控制权和生命周期。
unique_ptr
std::unique_ptr
是std::auto_ptr
的替代者。unique_ptr对自己持有的堆内存具有唯一的控制权,其禁止复制语义。在unique_ptr的实现中,其operator=运算符和复制构造函数被标记为了delete,如下:
1 | template<typename T> |
这使得将一个unique_ptr对象赋值给另一个unique_ptr,或者用一个unique_ptr初始化另一个unique_ptr对象的代码无法通过编译。
1 |
|
这里有一个例外,当从一个函数返回一个unique_ptr时,是可以的:
1 | unique_ptr<int> func_return_unique(){ |
既然禁止了复制语义,那这里又是怎样实现的呢?答案是移动。unique_ptr实现了移动赋值运算符和移动构造函数
1 | unique_ptr(const unique_ptr&&) |
这使得unique_ptr可以通过std::move()或者在函数中返回来转移其控制权。
1 |
|
当讲p1 move给p2后,p1就变成了一个空的智能指针对象。注意并不是所有对象的std::move操作都有意义,一定是实现了移动构造函数和移动赋值运算符的对象才行。
shared_ptr
unique_ptr独享其所占有的堆内存,而shared_ptr则允许其所占有的堆内存被多个变量共享,其使用了引用计数,每当shared_ptr被赋值,构造,从函数返回等,其引用计数加一;每一个指向其共享资源的shared_ptr析构时,其引用计数减一;当最后一个shared_ptr析构时,会连同析构掉其所持有的堆内存。
1 |
|
为了接着往下介绍weak_ptr,这里我们需要介绍std::enable_shared_from_this
1 | class C: public std::enable_shared_from_this<C>{ |
调用self方法,即可获得C类型对象的shared_ptr指针。
1 | int main(){ |
weak_ptr
1 | class C: public std::enable_shared_from_this<C>{ |
考虑上面的代码,类型C的对象c调用self()成员方法之后,导致成员变量_m与c之间循环引用。这使得shared_ptr的引用计数永远不能为0,从而导致内存泄漏。也就是说一个资源对象的生命周期可以交给一个智能指针对象管理,但是该智能指针不能再交给这个资源对象来管理。
weak_ptr是一个不获取资源生命周期的智能指针,是一种对资源对象的弱引用。它只提供了对其引用资源的访问手段,引入它的目的是协助std::shared_ptr工作。weak_ptr是用来解决上面的循环引用问题。如下所示:
1 | class C: public std::enable_shared_from_this<C>{ |
weak_ptr可以由一个shared_ptr或者另一个weak_ptr构造,一个weak_ptr可以调用lock()方法来获得shared_ptr.weak_ptr的构造和析构不会引起引用计数的增加或减少。
因为weak_ptr不管理其所引用对象的生命周期,所以其引用的对象在何时被销毁对weak_ptr不可见,所以要访问其所引用的对象,需要先通过其expired()方法检测对象是否被销毁,检测还存在,然后才能通过lock()方法获取shared_ptr对象,常见的访问代码如下:
1 | if (wp.expried()) return; |
weak_ptr没有实现operator->
、operator*
和opeator!
方法,所以不能直接访问其所引用的资源对象,不能解引用,不能用!符号判断引用的资源是否存在。
使用场景
auto_ptr已经被彻底废弃,无论何种情况都不应使用.如果你的对象不需要被共享应该优先使用unique_ptr,如果需要共享则使用shared_ptr,如果不需要管理资源的生命周期则使用unique_ptr.