C++11学习笔记——Dynamic memory management

来源:互联网 发布:windows live 账号注册 编辑:程序博客网 时间:2024/06/07 14:15

相比前面的类型支持(Type Support)部分,这部分的内容编程里相对来说使用频率更高,比如智能指针。

1.Smart pointers 智能指针

包含4个内容,unique_ptr/shared_ptr/weak_ptr/auto_ptr,其中前三个是C++11后纳入标准库的,更早的在boost或者<tr1/memory>中也有支持。

1.1 shared_ptr<T> 

       应该算是最常用的智能指针了,几乎所有的实现都采用在堆(heap)上放个计数值(count)的办法,当引用计数为0时则销毁其管理的资源,基本使用方法这里不再做多的说明。下面主要用代码的方式做一些特别说明:

1)避免如下的情况
A *a = new A(1);{    std::shared_ptr<A> p1(a);    std::shared_ptr<A> p2(a);}
        p1和p2的引用资源实际上是分开的!当程序离开{}块的时候,p1对象离开{}块后引用计数编程0,将指针a的资源释放;p2对象离开{}块后执行同样的操作,但是指针a已经被析构掉了!!指针a这样就被析构两次,程序出现堆错误。正确的用法是如下:
std::shared_ptr<A> p1(new A(1));std::cout << p1.use_count() << std::endl;</span>
{//这里只是为了测试引用计数加的大括号    std::shared_ptr<A> p2(p1);    std::cout << p1.use_count() << std::endl;}std::cout << p1.use_count() << std::endl;</span>

2)线程安全

      shared_ptr引用计数本身是安全且无锁的,但对象的读写则不是,实际上 shared_ptr 有两个数据成员,一个用于保存指向实际对象的指针,另一个则是引用计数器。在进行shared_ptr赋值、拷贝构造、析构(写操作)的时候分了两步——先将指针指向实际对象,然后在引用计数器上+1,这样必然导致操作不是原子的。在多线程同时读写同一个智能指针对象时就必须加锁:

std::shared_ptr<A> p(new A);std::shared_ptr p1;// thread Ap1 = p; //读取p// thread Bp.reset(); //如果线程A读取p的同时线程B对p进行了写操作,那么会导致未知行为。

类似的还有同时写的时候也要加锁:

std::shared_ptr<int> p;// thread Ap.reset(new int(1));// thread Bp.reset(new int(2)); //同时写入
或者:
// thread Ap1 = p; //增加一个引用计数// thread B// p同时已经离开了作用域,其管理的资源已经被析构了

1.2 unique_ptr<T>

       唯一指针,unique_ptr<T>的更换保管对象必须由std::move或者reset()成员函数去实现。相比原有的std::auto_ptr<T>不会莫名其妙的发现原有的保管对象被销毁的情况(更为安全)。相比于shared_ptr<T>,该指针可以支持动态数组。
       文档里对几种典型应用作了描述,概括起来就是对“生命周期内要求被唯一指针指向”的对象进行管理。说的比较抽象,本人倒是经常在开辟动态数组的时候使用:

int size = 10;std::unique_ptr<int[]> p1(new int[size]);

       当p1离开作用域时,会自动调用delete[]对开辟的内存进行释放,这也从某方面贯彻了《Effective C++》里所讲的以对象管理内存资源的思想,省的new了之后忘记delete造成内存泄露。当然复杂点的场景也可以自己制定deleter。

1.3 weak_ptr<T>

       配合shared_ptr<T>使用的一个智能指针,除了常规的拷贝构造外只能从一个已经存在的shared_ptr<T>上去构造。目的是防止shared_ptr<T>指向的对象被异步销毁后,再次使用造成访问已经销毁的内存。典型应用如菱形创建shared_ptr<T>。
A *a = new A(1);{    std::shared_ptr<A> p1(a);    std::shared_ptr<A> p2(a);}
      由于p1销毁后p2不知道a的资源已经被释放了,造成再次访问出现问题,但是感觉正常来讲一般不会出现该情况。本人曾写过一个脱离框架的面向对象的定时器,由于Timer里面需要指定回调,为了解耦方便,本人用了std::function<void ()>去代替,但是如果指定了回调函数timeout_,那么假如指向的回调函数所在的对象被销毁了(而且函数内部使用的资源也被销毁了),那么当计时到了的时候调用timeout_就会出问题。为了解决该问题本人想了个偷懒的办法,既想很好的解耦又想方便代码的书写,于是就使用shared_ptr和weak_ptr,代码如下:
class Timer{public:    void setTimeout(const std::shared_ptr<std::function<void ()> >& func)    { timeout_ = func; }    const std::weak_ptr<std::function<void ()> >& timeout() const    { return timeout_; }private:     std::weak_ptr<std::function<void ()> > timeout_; };
       
timeoutEvent(int timerfd){    ...        //如果回调对象被删除了,根据weak_ptr的expired进行判断是否回调对象已经没了        if (!iter->second->timeout().expired())        {            (*iter->second->timeout().lock())();        }    ...}




       



0 0
原创粉丝点击