Boost 第三章 内存管理--------智能指针库

本文章所有内容源于《BOOST程序库完全开发指南:深入C++“准”标准库(第3版)》第三章


1. smart_ptr库

背景知识:计算机系统中资源有很多,比如内存、文件描述符、socket、操作系统handle、数据库连接,这些资源申请以后需要归还给系统,不然就会出现难以预料的后果

智能指针

C++采用的是RAII机制(资源获取即初始化),意思就是构造函数申请资源,使用,最后析构函数中释放。这个时候内存占用就分为两种情况:

  1. 栈区
    一个局部对象,RAII机制正常工作,结束使用后自动销毁,并释放资源
  2. 堆区
    使用new创建,使用delete销毁。如果delete销毁错误,或者销毁失败,那么这部分内存资源就永久丢失。因此智能指针被创造,它可以在使用后,自动销毁空间。

头文件:
#include
using namespace boost;
智能指针种类:
scoped_ptr、scoped_array、shared_ptr、shared_array、weak_ptr、intrusive_ptr

2. scoped_ptr

2.1 功能:

包装了new操作符在堆上分配动态对象,保证动态创建对象可以正确删除。但是只能用于本作用于,不能被转让(禁止拷贝)。
成员函数:
swap():交换两个scoped_ptr保存的原始指针
get():返回指针内部保存的元史之真,用于底层C接口

2.2 用法:

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不能拷贝构造

2.3 对比unique_ptr

unique_ptr比scoped_ptr功能更多:可以像原始指针一样进行比较,可以像shared_ptr一样定制删除器,可以安全放入标准容器。建议用unique_ptr替代scoped_ptr。

3. scoped_array

3.1 功能:

包装new[]操作符(不是单纯的new),在堆区分配动态数组,保证可以正确的释放内存。它只能在被声明的作用域使用,不能拷贝、赋值。

3.2 用法:

scoped_array<int> sa( new int[100] );//包装动态数组

sa[0]=10; 													   //赋值

3.3 对比unique_ptr:

unique_ptr币scoped_array功能更多。scoped_array的功能有限,不能动态增长,没有边界检查,也没有迭代其支持,不推荐使用scoped_array。

4. shared_ptr

该指针需要好好学习,是有价值、最重要、最有用的指针。

4.1 功能:

shared_ptr引用了计数型的智能指针,可以被自由的拷贝和赋值,可以比较运算,任意的地方共享,当引用计数为0时,才删除被包装的动态分配的对象。shared_ptr也可以安全的放到标准容器中,是在STL容器存储指针的最标准解法。

4.2 初级用法:

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也同时被修改

4.3 高级用法:

注意:有时候会出现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
    */
}

4.4 重要:构造函数类型:

  • 无参的shared_ptr()创建一个持有空指针的shared_ptr;
  • shared_ptr(Y* p)获得指向T的指针p的管理权,同时引用计数置为1。这个构造函数要求Y类型必须能够转换为T类型;
  • shared_ptr(shared_ptr const & r)从另外一个shared_ptr获得指针的管理权,同时计数加1,结果是两个shared_ptr获得指针的管理权;
  • operator= 赋值操作符可以从另外一个shared_ptr获得指针的管理权,其行为同构造函数;
  • shared_ptr(Y * p , D d)行为类似shared_ptr(Y * p),但是使用参数d制定了析构时的定制删除器,而不是简单的delete;
  • 别名构造函数(aliasing),不增加引用计数的特殊用法。

注意:shared_ptr的reset()函数的作用是将引用计数减1,停止对指针的共享,除非引用计数为0,否则不会发生删除操作。带参数的reset()则类似相同形式的构造函数,原指针引用计数减1同时改为管理另一个指针。

shared_ptr中两个函数专门检查引用计数

  • unique()在该指针的唯一所有者时返回true
  • use_count()返回当前指针的引用计数

4.5 工厂函数make_shared:

功能:在使用智能指针的时候,可以不用写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
	*/
}

4.6 桥接模式bridge:

概念:桥接模式(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();
}

4.7 工厂模式:

概念:一种创建型设计模式,这个模式包装了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;
}

4.8 定制删除器:

概念: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,不用担心有资源丢失的问题。

4.9 高级议题

后续补充…

5. shard_array

5.1 功能与区别:

功能:shared_array类似shared_ptr,它包装了new[]操作符在堆上分配的动态数组,同样使用引用计数机制为动态数组提供了一个代理,在程序的声明周期里长期存在,知道没有任何引用才释放内存。
区别

  • 构造函数接收的指针p是new[]的结果,二不能是new表达式的结果
  • 可以像普通数组一样用下标访问元素
  • 没有*、->操作符重载,因为shared_array持有的不是一个普通指针
  • 析构函数用delete[]释放资源

5.2 用法:

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);
}																				//离开作用域时,自动删除动态数组

6. weak_ptr

6.1 功能:

为配合shared_ptr而引入的一种智能指针,它更像是shared_ptr的一个助手而不是智能指针,因为它不具有普通指针的行为,没有operator*和->。它的最大作用在于协助shared_ptr工作

6.2 用法:

注意:它只是协同工作,但是不会引起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将获得一个空指针

6.3 打破循环引用:

有时候代码中可能会出现“循环引用”,这时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,它不会增加引用计数,就可以打破循环。

7. intrusive_ptr

7.1 功能:

intrusive_ptr也是一种引用计数型智能指针,但与之前介绍的scoped_ptr和shared_ptr不同,需要额外增加代码才可以使用。如果现存代码已经有了引用计数机制管理对象,那么intrusive_ptr是一个非常好的选择,可以包装已有对象从而得到与shared_ptr类似的智能指针。

7.2 用法:

它的接口与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);

    }
}                                                                                                                                  //对象被正确析构

8. pool

背景知识:内存池预先分配了一块大的内存空间,然后就可以在其中使用某种算法实现高效快速的自定制内存分配。
boost.pool库基于简单分隔存储思想实现了一个快速、紧凑的内存池库,不仅能够管理大量对象,还可以用作STL的内存分配器。pool库包含四个组成部分:最简单的pool、分配类实例的object_pool、单件内存池singleton_pool和可用于标准库的pool_alloc。

8.1 功能:

用于管理内存池。

8.2 用法:

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。

9. object_pool

9.1 功能:

9.2 用法:

10. singleton_pool

10.1 功能:

10.2 用法:

11. pool_alloc

11.1 功能:

11.2 用法:

你可能感兴趣的:(BOOST,c++,boost,指针)