一、引入背景
使用 C++ 的指针可以动态开辟存储空间,但若在使用完毕后忘记释放(或在释放之前,程序 throw 出错误,导致没有释放),导致该内存单元一直被占据直到程序结束,即发生了所谓的内存泄漏。
【注】内存泄漏是指堆内存的泄漏。堆,就是那些由 new 分配的内存块。
因此智能指针的作用就是为了保证使用堆上对象的时候,对象一定会被释放,但只能释放一次,并且释放后指向该对象的指针应该马上归 0。
二、C++11 以前的智能指针
auto_ptr,指向一个动态分配的对象指针,它的析构函数用于删除所指对象的空间,以此达到对对象生存期的控制。
已被弃用,原因是:
- 避免潜在的内存崩溃:auto_ptr 进行赋值操作时候,被赋值的取得其所有权,去赋值的丢失其所有权(变成空指针,无法再使用);
- 不够方便:没有移动语义(后面讲)。
三、C++11 标准的智能指针
加入了 unique_ptr、shared_ptr 和 weak_ptr。
-
unique_ptr,等于 boost 库中的 scoped_ptr,正如其名字所述,scoped_ptr 所指向的对象在作用域之外会自动得到析构。
【注】boost 库是 Boost 社区在 C++11 之前嫌弃标准更新太慢,而自发组织开发、维护的一个扩展库。通过包含头文件”boost/scoped_ptr.hpp”
来引入。
简单定义:
namespace boost
{
// scoped_ptr 是 non-copyable 的
// 也就是说你不能去尝试复制一个 scoped_ptr 的内容到另外一个 scoped_ptr 中
// 这也是为了防止错误地多次析构同一个指针所指向的对象
template<typename T> class scoped_ptr : noncopyable
{
private:
T *px;
// scoped_ptr 不能通过其他 scoped_ptr 共享控制权
// 因此在 scoped_ptr 类的内部将拷贝构造函数和 = 运算符重载定义为 private
scoped_ptr(scoped_ptr const &);
scoped_ptr &operator=(scoped_ptr const &);
typedef scoped_ptr<T> this_type;
void operator==( scoped_ptr const & ) const;
void operator!=( scoped_ptr const & ) const;
public:
// 构造函数显示定义,不能通过隐式转换(赋值操作符 =)构造
explicit scoped_ptr(T *p = 0);
~scoped_ptr();
explicit scoped_ptr( std::auto_ptr<T> p ): px( p.release() );
// 在 scoped_ptr 离开作用域之前也可以显式销毁它们所管理的对象,调用它的 reset 方法即可
void reset(T *p = 0);
T &operator*() const;
T *operator->() const;
T *get() const;
// 虽然 scoped_ptr 不能转移控制权,但是它们可以交换控制权
void swap(scoped_ptr &b);
};
template<typename T>
void swap(scoped_ptr<T> &a, scoped_ptr<T> &b);
}
shared_ptr,可共享指针对象,可以赋值给 shared_ptr 或 weak_ptr。shared_ptr 中所实现的本质是引用计数,拷贝一个 shared_ptr 将对这个智能指针的引用次数加 1,而当这个智能指针的引用次数降低到 0 的时候,该对象自动被析构。
代码部分与 scoped_ptr 类似,只是支持拷贝、赋值和==、!=。weak_ptr
weak_ptr 和 shared_ptr 的最大区别在于 weak_ptr 在指向一个对象的时候不会增加其引用计数,因此你可以用 weak_ptr 去指向一个对象并且在 weak_ptr 仍然指向这个对象的时候析构它,此时你再访问 weak_ptr 的时候,weak_ptr 返回的会是一个空的 shared_ptr。
常被用来解决环状引用问题,告诉 C++ 环上哪一个引用是最弱的,因此在一个环上把原来的某一个 shared_ptr 改成 weak_ptr 就可以了。
【注】以上 std 标准库的智能指针都可以通过包含头文件 <memory>
来引入。
四、智能指针 unique_ptr 和 shared_ptr 的使用选择
区别
我们都知道 unique_ptr 是独占所指向对象的,也就是说某个时刻只能有一个 unique_ptr 指向一个给定对象。如果这个对象需要在多个指针之间分享,可以把它转变成 shared_ptr。如果是 unique_ptr,当它被销毁时它所指向的对象也被销毁。但 shared_ptr 会维护一个引用计数(use_count),只有最后一个 shared_ptr 也被销毁时,这个对象才真的被销毁。
使用场景
尽管 shared_ptr 功能强大,但不是任何时候都有必要使用 shared_ptr。当你不确定使用的指针是不是被分享所有权的时候,默认选 unique_ptr 独占式所有权,因为 unique_ptr 效率比 shared_ptr 高,不需要维护引用计数和背后的控制块。当确定要被分享的时候可以转换成 shared_ptr。
另外其实不是说两个指针分享同一个变量就需要用 shared_ptr,尤其是不涉及到多线程的程序中,不使用智能指针也没关系。
有必要使用 shared_ptr 的场景举例:
----------------------------------------
| Thread 1 |
| ------------------------------------ |
| void func(shared_ptr<int> p) { ... } |
----------------------------------------
-------------------------------------------
| Main Thread |
| --------------------------------------- |
| shared_ptr<int> p = make_shared<int>(); |
| func(p); |
-------------------------------------------
主线程开启子线程并在其中执行 func 函数,如果不使用智能指针 shared_ptr,当主线程执行完自行释放普通指针 p,此时子线程的函数还没执行完,p 就已经失效了,造成程序崩溃。而使用 shared_ptr 只会在主线程结束时自动将 use_count 减一,不影响子线程的使用。
参考文献:
[1] shared_ptr 和 unique_ptr 区别和联系
[2] 智能指针:shared_ptr 和 unique_ptr,优先使用 unique_ptr