C++ 使用智能指针的必要性
评判智能指针的标准
在C/C++中,指针很重要,却是麻烦的来源。如果一个指针建立是没有初始化,那就是野指针(Vaild Pointer);如果一个指针指向已经释放的内存,那就是空悬指针(Dangling Pointer)。
一些比 C/C++ 更高级的语言都要极力避开指针,比如 Java 使用了垃圾回收,让系统自动回收用户申请的资源。
C++ 的智能指针,始于 Boost 准标准库,在 C++11 正式加入到C++标准模板库(STL)中。
智能指针的核心思想是:“ 程序员负责申请资源,释放留给智能指针 ”。所以我们可以最直观的感受到智能指针的好处是:“ 申请资源一劳永逸,老板再也不用担心内存泄露 ”。
但如果我们对智能指针的理解仅限于此,我们就会受到很多反对的声音的干扰。
- “ 当年的 auto_ptr 多么垃圾 ”
- 智能会带来许多额外的开销,C/C++的效率高的优势荡然无存
- 智能指针的风格和原来的 new 格格不入
- 遇到某些复杂的数据结构,或者所有权不明确的场景,还是得裸指针来
但是,如果仅仅将智能理解为防止内存泄露,是十分片面的。在考虑是否使用智能指针的时候,我们应该看到,该项技术到底是弊大于利还是利大于弊?
多线程的情况
如果我们考虑到多线程的情况,那么智能指针的优势几乎是压倒性的。
如今有一个多线程的程序,多个线程共同持有一个对象。那么:
(1) 如何确保在对象析构的时候,没有其他线程正在使用对象资源呢?
(2) 如果确保在对象析构后,没有其他线程会后续使用对象资源呢?
对于第一个问题,我们可以在对象外部建立一套锁机制,在对象析构和引用对象的时候通过锁来确保排他性访问。
对于第二个问题,我们也可以在对象外部奖励一套信号量机制,每当有线程应用对象,便对信号量进行P操作来计数,使用完毕时进行V操作。
但是,如果对于每个共享资源,我们新建一套保护机制,那么对于程序员来说是非常大的负担,同时也容易出错。
智能指针恰恰帮程序员解决了这一痛点,因为智能指针有本身就是通过一套原子性的计数机制实现的。
下面是一些 shared_ptr 的笔记。
shared_ptr 的申请对象
使用 shared_ptr 申请对象最好的方法是通过 make_shared<T>(args)
函数返回 shared_ptr。其中 args 是对象的构造函数的参数。
shared_ptr<T> sp = make_shared<T>( args );
// 相当于 T *p = new T( args );
shared_ptr 的拷贝构造
- 如果已经申请对象,可以用拷贝构造使多个 shared_ptr 指向同一个对象。
- 当进行拷贝或赋值操作时,每个shared_ptr都会记录有多少个其他shared_ptr指向相同的对象。
- 当我们给shared_ptr赋予一个新值或是shared_ptr被销毁时,计数器就会递减。
- 当最后一个对象的最后一个shared_ptr被销毁时,shared_ptr类会自动销毁此对象。
shared_ptr<T> p = make_shared<T>( args );
shared_ptr<T> q(p); /* p和q指向同一个对象,计数器加1 */
shared_ptr 作为判断条件
shared_ptr 对象可以直接用于作为条件判断,如果p指向一个对象,则为true,否则为false。
shared_ptr<T> sp = make_shared<T>( args );
if( sp ) {
cout << "true" << endl;
}
else {
cout << "false" << endl;
}
shared_ptr 解引用
shared_ptr 在解引用上,和 c/c++ 指针是一致的。
shared_ptr<T> sp = make_shared<T>( args );
*sp;
sp -> f();
shared_ptr 独占判断
如果只有一个 shared_ptr 引用了对象,那么 unique() 返回 true,否则返回 false。
shared_ptr<T> sp = make_shared<T>( args );
if( sp.unique() ) {
cout << "true" << endl;
}
else {
cout << "false" << endl;
}
返回c/c++指针
- 有些时候,只能使用c/c++指针。可以用成员函数get()来返回shared_ptr对应的c/c++指针。
- 但是 get() 是不会增加计数器的,get()返回的指针,有可能在将来成为空悬指针。
- 此外,函数 get() 返回的指针是不能 delete 的。
shared_ptr<T> sp = make_shared<T>( args );
T *p = sp.get();
附属品 weak_ptr
weak_ptr 是一种不控制所指向对象生存期的智能指针,它指向由一个 shared_ptr 管理的对象。将 weak_ptr 绑定到一个 shared_ptr 不会改变 shared_ptr 的引用计数。
一旦最后一个指向对象的shared_ptr被销毁,对象就会被释放。即使有 weak_ptr 指向对象,对象还是会被释放,因此,weak_ptr 的名字抓住了这种智能指针“弱”共享对象的特点。
- 当我们创建一个 weak_ptr 时,要用一个 shared_ptr 来初始化它:
shared_ptr<T> sp = make_shared<T>( args );
/* 使用shared_ptr来初始化weak_ptr而shared_ptr的计数值不变 */
weak_ptr<T> wp( sp );
- 由于对象可能不存在,我们不能使用weak_ptr直接访问对象,而必须调用lock()。
- 此函数检查weak_ptr 指向的对象是否仍存在。如果存在,lock() 返回一个指向共享对象的shared_ptr。
- 与任何其他shared_ptr类似,只要此shared_ptr存在,它所指向的底层对象也就会一直存在。
share_ptr<T> sp = make_shared<T>( args );
weak_ptr<T> wp( sp );
if( shared_ptr<T> np = wp.lock() ) { // 如果np不为空则条件成立
// 如果wp指向的对象存在,就将对象交给np,计数值加1
// 因此在if中,该对象一直有效
}