为管理内存等资源,C++采取RAII机制(资源获取即初始化,Resource Acquisition Is Initialization),在使用资源的类的构造函数中申请资源并使用,最终在析构函数中释放资源。使用new在堆上创建对象时,其析构函数不会自动调用,需要使用delete才能释放资源,若因为异常导致程序未能执行delete,则存在内存泄露的问题。C++98标准中的“自动指针std::auto_ptr(C++11中废弃,改用unique_ptr)部分解决了获取资源自动释放的问题。
boost.smart_ptr库提供六种智能指针,包括socped_ptr、scoped_array、shared_ptr、shared_array、weak_ptr和intrusive_ptr。他们都是轻量级的对象,速度与原始指针相差无几,都是异常安全的,而且对于所指向的类型T也仅有一个很小且合理的要求:类型T的析构函数不能抛出异常。
头文件:#include
scoped_ptr | 保证智能指针只能在本作用域中使用,拥有对象的唯一所有权,不可以复制。如果一个类中有scoped_ptr成员变量,则该类也不可拷贝或赋值。 | |
scoped_array | 类似scoped_ptr,只不过包装的是new[ ]操作符,不推荐使用 | |
shared_ptr | 实现了引用计数,可以拷贝或赋值,当引用计数为0时自动删除动态分配的对象 | |
shared_array | 类似shared_ptr,但包装的是new[ ]分配的动态数组 | |
weak_ptr | 配合shared_ptr而引入,不具备普通指针的行为,作为shared_ptr拥有对象的非拥有观察者 | |
intrusive_ptr | 引用计数型智能指针,可以包装已有对象得到与shar_ptr类似的智能指针 |
scoped_ptr包装了new操作符在堆上分配的动态对象,能够保证动态创建的对象在任何时候都可以被正确地删除,scoped_ptr获取对象管理权后就不能再转让。
scoped_ptr的构造函数接受一个类型为T*的指针p,创建出一个scoped_ptr对象,并在内部保存指针参数p。p必须是new表达式动态分配的结果,或者是一个空指针(nullptr)。当scoped_ptr对象的生命期结束时,析构函数~scoped_ptr()会使用delete操作符自动销毁所保存的指针对象来正确回收资源。拷贝构造函数和赋值操作符被声明为私有的,禁止对智能指针的拷贝操作,保证了被它管理的指针不能被转让所有权。
scoped_ptr提供在bool语境(如if的条件表达式)中自动转换成bool值的功能,用来测试scoped_ptr是否持有一个有效的指针(非空),可以替代与空指针的比较操作(有限比较,仅能与空指针进行比较操作)。
#include
using namespace boost;
struct posix_file{ //一个示范性质的文件类
posix_file(const char *file_name){
cout << "open file: " << file_name << endl;
}
~posix_file(){
cout << "close file" << endl;
}
}
int main(){
//文件类的scoped_ptr,将在离开作用域时自动析构,从而关闭文件释放资源
scopoed_ptr fp(new posix_file("/tmp/a.txt"));
scoped_ptr p(new int); //一个int指针的scoped_ptr
if(p){ //在bool语境中测试指针是否有效
*p = 100; //可以像普通指针一样使用解引用操作符*
cout << *P <
包装了new[ ]操作符在堆上分配的动态数组,为动态数组提供了一个代理,来保证正确释放内存。主要特点:
#include
using namespace boost;
int main(){
int *arr = new int[100]; //一个整数的动态数组
scoped_array sa(arr); //scoped_array对象代理原始动态数组
fill_n(&sa[0],100,5); //可以使用标准库算法赋值数据
sa[10] = sa[20] + sa[30]; //用起来就像是个普通数组
} //这里scoped_array被自动析构,释放动态数组资源
shared_ptr是最像指针的”智能指针“,是boost.smart_ptr库中最有价值、最重要、最有用的组成部分。他包装了new操作符在堆上分配的动态对象,实现了引用计数,可以自由地拷贝和赋值,在任意地方进行共享。也可以安全地放到标准容器中,是在STL容器中储存指针的最标准解法。
shared_ptr有多种形式的构造函数,应用于可能的情形:
reset()函数将shared_ptr的引用计数减1,停止对指针的共享,除非引用计数为0,否则不会发生删除操作;带参数的reset()类似相同形式的构造函数,原引用计数减1的同时改为管理另一个指针。
unique()和use_count()专门用来检查引用计数,unique()在指针是唯一所有者时返回true,是可靠的,任何时候都可用;use_count()返回当前指针的引用计数,不提供高效率的操作。
shared_ptr支持比较运算,可以测试两个shared_ptr的相等或不相等,比较基于内部保存的指针,相当于a.get() == b.get();还可以使用operator<比较大小,但不提供除此外的比较操作符。
此外shared_ptr还支持流输出操作符operator<< ,输出内部的指针值,方便调试。
class shared{ //一个拥有shared_ptr的类
private:
shared_ptr p; //shared_ptr成员变量
public:
shared(shared_ptr p_):p(p_){ //构造函数初始化shared_ptr
}
void print(){ //输出shared_ptr的引用计数和指向的值
cout << "count: " << p.use_count()
<< "v = " << *p < p){ //使用shared_ptr作为函数参数
cout << "count: " << p.use_count() //同样输出引用计数和指向的值
<< "v = " << *p < p(new int(100))
shared s1(p),s2(p); //构造两个自定义类
s1.print();
s2.print();
*p = 20; //修改shared_ptr所指的值
print_func(p);
s1.print();
}
工厂函数make_shared()可以接受若干参数,然后把它们传递给类型T的构造函数,创建一个shared_ptr
auto sp = make_shared("make_shared"); //创建string的共享指针
auto spv = make_shared>(10,2); //创建vector的共享指针
shared_ptr应用于标准容器、应用于桥接模式、应用于工厂函数、定制删除器.......
shared_array与shared_ptr基本相同,具有shared_ptr的优点和scoped_array的缺点,主要区别:
#include
using namespace boost;
int main{
int *p = new [100]; //一个动态数组
shared_array sa(p); //shared_array代理动态数组
assert(sa.unique()); //唯一持有指针
shared_array sa2 = sa; //共享数组,引用计数增加
assert(sa2.use_count()==2); //引用计数增加
sa[0] = 10; //可以使用operator[]访问元素
assert(sa2[0] == 10);
} //离开作用域,自动删除动态数组
shared_array不提供数组索引的范围检查,如果使用超过动态数组大小的索引或负索引将引发未定义行为。
weak_ptr被设计与shared_ptr协同工作,可以从一个shared_ptr或另一个weak_ptr对象构造,获得资源的观察权,构造和析构不会引起指针引用计数的增加或减少。
use_count()可以观测资源的引用计数,expired()的功能等价于use_count()==0,但更快,表示被观测的资源不复存在。lock() 从被观测的shared_ptr获得一个可用的shared_ptr对象,把弱关系转换为强关系,从而操作资源,当expired()==true时,lock()返回空指针的shared_ptr。
weak_ptr一个重要用途是获取this指针的shared_ptr,使对象能够自己生产shared_ptr管理自己:对象使用weak_ptr观测this指针,这并不影响引用计数,需要时调用lock()函数,返回一个符合要求的shared_ptr供外界使用。成员函数shared_from_this()会返回this的shared_ptr。使用方法:
class self_shared :
public enable_shared_from_this{
pulic:
self_shared(int n):x(n){
}
int x;
void print(){
cout << "self_shared: " << x << endl;
}
};
int main(){
auto sp = make_shared(313);
sp -> print();
auto p = sp ->shared_from_this(); //返回this的shared_ptr
p -> x = 1000;
p -> print();
}
与enable_shared_from_this类似,但不要求对象必须被一个shared_ptr管理,可以直接从一个原始指针创建出shared_ptr。
#include
class raw_shared : public boost::enable_shared_from_raw{
public:
raw_shared(){
cout << "raw_shared ctor" << endl;
}
~raw_shared(){
cout << "raw_shared dtor" << endl;
}
};
int main(){
raw_shared x; //一个普通对象
asser(!weak_from_raw(&x).use_count()); //此时无引用,注意要用&取地址
auto px = shared_from_raw(&x); //获取shared_ptr
assert(px.use_count()==2); //引用计数为2!
} //对象自动删除
代码中出现“循环引用”时,shared_ptr的引用机制会失效,导致不能正确析构释放资源。使用weak_ptr在可能存在循环引用的地方打破循环,在需要shared_ptr的时候调用weak_ptr的lock()函数。
class node{ //链表节点的类
public:
...
typeder weak_ptr ptr_type; //指针类型使用weak_ptr
ptr_type next; //后继指针
};
int main(){
auto p1 = make_shared(); //两个节点对象
auto p2 = make_shared();
p1 -> next = p2; //形成循环链表
p2 -> next = p1; //引用使用了weak_ptr所以正常
assert(p1.use_count() == 1); //每个shared_ptr的引用计数是1
assert(p2.use_count() == 1); //没有了循环引用
if(!p1 -> next.expired()){ //检查弱引用是否有效
auto p3 = p1 -> next.lock(); //调用lock()获得强引用
}
} //退出作用域,shared_ptr均正确析构
intrusive_ptr接口与shared_ptr很像,同样支持比较和static_pointer_cast()、dynamic_pointer_cast()等转型操作,但不直接管理引用计数,而是调用函数来间接管理:
void intrusive_ptr_add_ref(T *p); //增加引用计数
void intrusive_ptr_release(T *p); //减少引用计数
intrusive_ptr构造函数和reset()多出一个bool add_ref参数,表示是否增加引用计数,如果add_ref == true,那么它就相当于weak_ptr,只是简单地观察对象。
参考文档:
BOOST程序库完全开发指南:深入C++“准”标准库
如何理解智能指针? 知乎
C++智能指针简单剖析
注:
assert是C/C++提供的准确性验证、测试支持的宏,详见 BOOST程序库完全开发指南第6章:正确性与测试