本文章所有内容源于《BOOST程序库完全开发指南:深入C++“准”标准库(第3版)》第三章
背景知识:计算机系统中资源有很多,比如内存、文件描述符、socket、操作系统handle、数据库连接,这些资源申请以后需要归还给系统,不然就会出现难以预料的后果
C++采用的是RAII机制(资源获取即初始化),意思就是构造函数申请资源,使用,最后析构函数中释放。这个时候内存占用就分为两种情况:
头文件:
#include
using namespace boost;
智能指针种类:
scoped_ptr、scoped_array、shared_ptr、shared_array、weak_ptr、intrusive_ptr
包装了new操作符在堆上分配动态对象,保证动态创建对象可以正确删除。但是只能用于本作用于,不能被转让(禁止拷贝)。
成员函数:
swap():交换两个scoped_ptr保存的原始指针
get():返回指针内部保存的元史之真,用于底层C接口
scoped_ptr<string> sp( new string( "text" ) ); //构造一个scoped_ptr对象
assert( sp ); //使用显式bool转型
assert( sp != nullptr ); //空指针比较操作
cout << *sp << endl; //取字符串的内容
cout << sp->size() << endl; //取字符串长度
scoped_ptr<string> sp2 =sp; //错误,scoped_ptr不能拷贝构造
unique_ptr比scoped_ptr功能更多:可以像原始指针一样进行比较,可以像shared_ptr一样定制删除器,可以安全放入标准容器。建议用unique_ptr替代scoped_ptr。
包装new[]操作符(不是单纯的new),在堆区分配动态数组,保证可以正确的释放内存。它只能在被声明的作用域使用,不能拷贝、赋值。
scoped_array<int> sa( new int[100] );//包装动态数组
sa[0]=10; //赋值
unique_ptr币scoped_array功能更多。scoped_array的功能有限,不能动态增长,没有边界检查,也没有迭代其支持,不推荐使用scoped_array。
该指针需要好好学习,是有价值、最重要、最有用的指针。
shared_ptr引用了计数型的智能指针,可以被自由的拷贝和赋值,可以比较运算,任意的地方共享,当引用计数为0时,才删除被包装的动态分配的对象。shared_ptr也可以安全的放到标准容器中,是在STL容器存储指针的最标准解法。
shraed_ptr<int> spi( new int ); //一个int的shared_ptr
assert(spi); //在bool语境中转换为bool值
*spi = 253; //使用解引用操作符*
shared_ptr<string> spt( new string("smart") ); //一个string的shared_ptr
assert(sps->size() == 5); //使用箭头操作符->
shraed_ptr<int> dont_do_this( new int [10] ); //危险!不要这么做
shared_ptr<int> sp(new int(10)); //一个指向整数的shared_ptr
assert(sp.unique()); //现在shared_ptr是指针的唯一持有者
shred_ptr<int> sp2 = sp; //第二个shared_ptr,拷贝构造函数
assert(sp == sp2 && //两个shared_ptr相等
sp.use_count() == 2); //指向同一个对象,引用计数为2
*sp2 = 100; //使用解引用操作符修改被指对象
assert(*sp == 100); //另一个shared_ptr也同时被修改
注意:有时候会出现shared_ptr不明确,是由于boost的一些库重复定义冲突了,注销掉就可以
#include
// using namespace boost; //shared_ptr不明确,是由于boost的一些库重复定义冲突了
using namespace std;
#include
class shared
{
private:
shared_ptr<int> p;
public:
shared(shared_ptr<int> p_) : p(p_) { } //构造函数初始化shared_ptr
void print()
{
std::cout << "count:" << p.use_count() //输出shared_ptr的引用计数和指向的值
<< " v=" <<*p << std::endl;
}
};
void print_func(shared_ptr<int> p) //使用shared_ptr作为函数参数
{
std::cout << "count:" << p.use_count() //同样输出引用计数和指向的值
<< " v=" <<*p << std::endl;
}
int main()
{
shared_ptr<int> p( new int(100) );
shared s1(p), s2(p); //构造两个自定义类
s1.print();
s2.print();
*p = 20; //修改shared_ptr所指的值
print_func(p);
s1.print();
/*
count:3 v=100
count:3 v=100
count:4 v=20
count:3 v=20
*/
}
注意:shared_ptr的reset()函数的作用是将引用计数减1,停止对指针的共享,除非引用计数为0,否则不会发生删除操作。带参数的reset()则类似相同形式的构造函数,原指针引用计数减1同时改为管理另一个指针。
shared_ptr中两个函数专门检查引用计数:
功能:在使用智能指针的时候,可以不用写delete析构了,但是还需要写new,通过工厂函数初始化的时候,就可以不用写new了。
#include
using namespace std;
#include
#include
int main()
{
typedef std::vector<shared_ptr<int> > vs; //一个持有shared_ptr的标准容器类型
vs v(10); //声明一个拥有10个元素的容器
//元素初始化为空指针
int i = 0;
/*-------------------两次解引用:方法1------------------------------*/
for (auto pos = v.begin(); pos != v.end(); ++pos)
{
(*pos) = make_shared<int>(++i); //使用工厂函数赋值
std::cout << *(*pos) << ", "; //**pos
}
std::cout << std::endl;
/*------------------------推荐:方法2------------------------------*/
for (auto& ptr : v)
{
ptr = make_shared<int>(++i); //使用for可以避免迭代器到shared_ptr的两次解引用,以后用该种遍历方法
std::cout << *ptr << ", ";
}
std::cout << std::endl;
shared_ptr<int> p = v[9];
*p = 100;
std::cout << *v[9] << std::endl;
/*
1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
11, 12, 13, 14, 15, 16, 17, 18, 19, 20,
100
*/
}
概念:桥接模式(bridge)是一种结构型设计模式,它把类的具体实现细节对用户隐藏起来,以达到类之间的最小耦合关系。在具体编程时间中桥接模式也被称为pimpl或者handle/body惯用法,它可以将头文件的依赖关系降到最小,较少编译时间,而且可以不是用虚函数实现多态。
功能:可以任意改变具体的实现而外界对此一无所知,同时也减小了源文件之间的编译依赖,使程序获得更多的灵活性。
#include
using namespace std;
#include
#include
class sample
{
private:
class impl; //不完整的内部类声明
shared_ptr<impl> p; //shared_ptr成员变量
public:
sample(); //构造函数
void print(); //提供给外界的接口
};
//在sample的cpp中完整定义impl类和其他功能
class sample::impl
{
public:
void print()
{ std::cout << "impl print" << std::endl;}
};
sample::sample() : p (new impl) {} //构造函数初始化shared_ptr
void sample::print() //构造pimpl实现print()
{ p->print();}
int main()
{
sample s;
s.print();
}
概念:一种创建型设计模式,这个模式包装了new操作符的使用,使对象的创建工作集中在工厂类或者工厂函数中,更容易适应变化。
#include
using namespace std;
#include
#include
// using namespace boost;
class abstract //接口类定义
{
public:
virtual void f() = 0;
virtual void g() = 0;
protected:
virtual ~abstract() = default; //注意这里
//abstract的析构函数,被定义为保护的,意味着除了它自己和它的子类
//其他任何对象都无权调用delete来删除它
};
class impl:public abstract
{
public:
impl() = default;
virtual ~impl() = default;
public:
virtual void f()
{ std::cout << "class impl f" << std::endl; }
virtual void g()
{ std::cout << "class impl g" << std::endl; }
};
//工厂函数返回基类的shared_ptr
shared_ptr<abstract> create()
{ return make_shared<impl>();}
//{ return shared_ptr(new impl);}
int main()
{
auto p = create(); //工厂函数创建对象
p->f(); //可以像普通指针一样使用
p->g(); //不用担心资源泄漏
abstract *q = p.get(); //使用get获得原始指针
// boost::ignore_unused(q);
//delete q;
}
概念:shared_ptr(Y * p, D d)的第一个参数是要被管理的指针,它的含义与其他构造函数的参数相同。而第二个删除器参数d则告诉shared_ptr在析构时,不是用delete来操作指针p,而使用d来操作,即把delete p换成d(p)。
有了删除器的概念,我们就可以用shared_ptr实现管理任意资源。只要资源提供了它自己的释放操作,shared_ptr就能够自动释放。
#include
using namespace std;
#include
#include
class socket_t {};
socket_t* open_socket() //打开socket
{
cout << "open_socket" << endl;
return new socket_t;
}
void close_socket( socket_t * s ) //关闭socket
{
cout << "close_socket" << endl;
}
int main()
{
socket_t *s = open_socket();
shared_ptr<socket_t> p(s, close_socket); //传入删除器
//shared_ptr p(s, &close_socket);//也可以这么写
}
这样我们就是用shared_ptr配合定制的删除器管理socket资源。当离开作用域时,shared_ptr会自动调用close_socket()函数关闭socket,不用担心有资源丢失的问题。
后续补充…
功能:shared_array类似shared_ptr,它包装了new[]操作符在堆上分配的动态数组,同样使用引用计数机制为动态数组提供了一个代理,在程序的声明周期里长期存在,知道没有任何引用才释放内存。
区别:
using namespace std;
#include
using namespace boost;
int main()
{
int *p = new int[100]; //一个动态数组
shared_array<int> sa(p); //shared_array代理动态数组
assert(sa.unique()); //唯一持有指针
shared_array<int> sa2 = sa; //共享数组,引用计数增加
assert(sa2.use_count() == 2); //引用计数增加
sa[0] = 10; //可以像普通数组一样访问元素
assert(sa2[0] == 10);
} //离开作用域时,自动删除动态数组
为配合shared_ptr而引入的一种智能指针,它更像是shared_ptr的一个助手而不是智能指针,因为它不具有普通指针的行为,没有operator*和->。它的最大作用在于协助shared_ptr工作。
注意:它只是协同工作,但是不会引起shared_ptr计数的增减。
虽然它不能通过*、->操作资源,但是可以通过成员函数lock(),从被观测的shared_ptr获得一个可用的shared_ptr对象,进而操作资源。
shared_ptr<int> sp( new int(10) ); //一个shared_ptr
assert(sp.use_const() == 1);
weak_ptr<int> wp(sp); //从shared_ptr创建weak_ptr
assert(wp.use_count() == 1); //weak_ptr不影响引用计数
if (!wp.expired()) //判断观察的对象是否失效
{
shared_ptr<int> sp2 = wp.lock(); //获得一个shared_ptr
*sp2 = 100;
assert(wp.use_count() == 2);
} //退出作用域,sp2自动析构,引用计数减1
assert(wp.use_count() == 1);
sp.reset(); //shared_ptr失效
assert(wp.expired()0
assert(!wp.lock()); //weak_ptr将获得一个空指针
有时候代码中可能会出现“循环引用”,这时shared_ptr的引用计数机制就会失效,导致不能正确释放资源。
更改前:
#include
using namespace std;
#include
class node //一个用于链表节点的类
{
public:
~node() //析构函数输出信息
{ std::cout << "deleted" << std::endl;}
// typedef weak_ptr ptr_type; //指针类型
typedef shared_ptr<node> ptr_type;
ptr_type next; //后继指针
};
int main()
{
auto p1 = make_shared<node>(); //两个节点对象
auto p2 = make_shared<node>();
p1->next = p2; //形成循环列表
p2->next = p1;
assert(p1.use_count() == 1);
assert(p2.use_count() == 1);
// if(!p1->next.expired())
// {
// auto p3 = p1->next.lock();
// }
}
更改后:
#include
using namespace std;
#include
// using namespace boost;
class node //一个用于链表节点的类
{
public:
~node() //析构函数输出信息
{ std::cout << "deleted" << std::endl;}
typedef weak_ptr<node> ptr_type; //指针类型
//typedef shared_ptr ptr_type;
ptr_type next; //后继指针
};
int main()
{
auto p1 = make_shared<node>(); //两个节点对象
auto p2 = make_shared<node>();
p1->next = p2; //形成循环列表
p2->next = p1;
assert(p1.use_count() == 1);
assert(p2.use_count() == 1);
if(!p1->next.expired())
{
auto p3 = p1->next.lock();
}
}
更改前,两个节点对象互相持有对方的引用,每一个shared_ptr的引用计数都是2,因此析构时引用计数没有减少到0,导致内存泄漏。所以我们用weak_ptr,它不会增加引用计数,就可以打破循环。
intrusive_ptr也是一种引用计数型智能指针,但与之前介绍的scoped_ptr和shared_ptr不同,需要额外增加代码才可以使用。如果现存代码已经有了引用计数机制管理对象,那么intrusive_ptr是一个非常好的选择,可以包装已有对象从而得到与shared_ptr类似的智能指针。
它的接口与shared_ptr很像,也同样支持比较和static_ponter_cast()、dynamic_pointer_cast()等转型操作,但它自己不直接管理引用计数,而是调用下面两个函数管理:
void intrusive_ptr_add_ref(T * p); //增加引用计数
void intrusive_ptr_release( T * p ); //减少引用计数
using namespace std;
#include
#include
using namespace boost;
struct counted_data
{
// ...
int m_count = 0;
~counted_data()
{
cout << "dtor" << endl;
}
};
void intrusive_ptr_add_ref(counted_data* p)
{
++p->m_count;
}
void intrusive_ptr_release(counted_data* p)
{
if(--p->m_count == 0)
{
delete p;
}
}
//
#include
struct counted_data2 : public intrusive_ref_counter<counted_data2>
{
~counted_data2()
{
cout << "dtor2" << endl;
}
};
//
int main()
{
typedef intrusive_ptr<counted_data> counted_ptr; //类型定义
counted_ptr p(new counted_data); //创建智能指针
assert(p); //bool转型
assert(p->m_count == 1); //operator->
counted_ptr p2(p); //指针拷贝构造
assert(p->m_count == 2); //引用计数增加
counted_ptr weak_p(p.get(), false); //弱引用
assert(weak_p->m_count == 2); //引用计数不增加
p2.reset(); //复位指针
assert(!p2); //p2不持有指针
assert(p->m_count == 1); //引用计数减少
{
typedef intrusive_ptr<counted_data2> counted_ptr;
counted_ptr p(new counted_data2);
assert(p);
assert(p->use_count() == 1);
}
} //对象被正确析构
背景知识:内存池预先分配了一块大的内存空间,然后就可以在其中使用某种算法实现高效快速的自定制内存分配。
boost.pool库基于简单分隔存储思想实现了一个快速、紧凑的内存池库,不仅能够管理大量对象,还可以用作STL的内存分配器。pool库包含四个组成部分:最简单的pool、分配类实例的object_pool、单件内存池singleton_pool和可用于标准库的pool_alloc。
用于管理内存池。
pool可以像C中malloc()一样分配内存,然后随意使用。除非有特殊要求,否则不必对分配的内存调用free()释放,pool会很好管理内存。
#define BOOST_SYSTEM_NO_DEPRECATED //避免链接boost.system库
#include
using namespace boost;
int main()
{
pool<> pl(sizeof(int)); //一个可分配int的内存池
int *p = static_cast<int*>(pl.malloc()); //把void*转换成需要的类型
assert(pl.is_from(p));
pl.free(p); //释放内存池分配的内存块
for (int i = 0;i < 100; ++i) //连续分配大量内存
{ pl.ordered_malloc(10); }
}
pool<>只能作为普通数据类型如int、double等的内存池,不能引用与复杂的类和对象,因为它只分配内存,不调用构造函数,这个时候我们需要用object_pool。