详解Boost库智能指针(shared_ptr && scoped_ptr && weak_ptr )

我们先来解释一下什么叫智能指针?

智能指针是利用RAII(在对象的构造函数中执行资源的获取(指针的初始化),在析构函数中释放(delete 指针):这种技法把它称之为RAII(Resource Acquisition Is Initialization:资源获取即初始化))来管理资源。

其本质思想是:将堆对象的生存期用栈对象(智能指针)来管理。也就是当new一个堆对象的时候,立刻用智能指针来接管,具体做法是在构造函数中进行初始化(用一个指针指向堆对象),在析构函数调用delete来释放堆对象。由于智能指针本身是一个栈对象,它的作用域结束的时候会自动调用析构函数,从而调用delete释放了堆对象。

这样,堆对象就由智能指针(栈对象)管理了,不容易发生内存泄露或者是野指针。

在讲解Boost库的智能指针之前,我们先来说一下STL中的auto_ptr,他的使用根据Effective STL 上的条款有着很多的限制:

1. auto_ptr不能共享所有权

2. auto_ptr不能指向数组

3. auto_ptr不能作为容器的成员

4. 不能通过赋值操作来初始化auto_ptr

std::auto_ptr<int> p(new int(42)); //ok

std::auto_ptr<int> p=new int(42); //error

这是因为auto_ptr的构造函数被定义成为了explicit

5. 不要把auto_ptr放入容器


由于限制太多并且使用不方便,所以我推荐使用Boost库的智能指针(Boost库下载后只需简单配置即可在vs中使用):

详解Boost库智能指针(shared_ptr && scoped_ptr && weak_ptr )_第1张图片

(一) scoped_ptr<T>

#include <boost/scoped_ptr.hpp>
#include <iostream>
using namespace std;

class X
{
public:
    X()
    {
        cout << "X ..." << endl;
    }
    ~X()
    {
        cout << "~X ..." << endl;
    }
};

int main(void)
{
    cout << "Entering main ..." << endl;
    {
        boost::scoped_ptr<X> pp(new X);

        //boost::scoped_ptr<X> p2(pp); //Error:所有权不能转移
    }
    cout << "Exiting main ..." << endl;

    return 0;
}
运行结果:

Entering main ...

X ..

~X ...

Exiting main ....

Scoped_ptr不能够被拷贝和赋值,但是可以交换。不能够被拷贝和赋值的原因是赋值运算符和拷贝构造函数都是private。

reset()也可以释放堆对象,就是利用交换实现的。

void reset(T *p = 0)  // never throws
{
    BOOST_ASSERT( p == 0 || p != px ); // catch self-reset errors
    this_type(p).swap(*this);
}
void swap(scoped_ptr &b)  // never throws
{
    T *tmp = b.px;
    b.px = px;
    px = tmp;
}

typedef scoped_ptr<T> this_type;  当调用pp.reset(),reset 函数构造一个临时对象,它的成员px=0, 在swap 函数中调换 pp.px  与 

(this_type)(p).px, 即现在智能指针pp.px = 0; //解绑  

临时对象接管了原生指针(即所有权可以交换),reset 函数返回,离开了函数作用域,栈上的临时对象析构,调用析构函数,进而delete px;

(二)shared_ptr<T>
#include <boost/shared_ptr.hpp>
#include <iostream>
using namespace std;

class X
{
public:
    X()
    {
        cout << "X ..." << endl;
    }
    ~X()
    {
        cout << "~X ..." << endl;
    }
};

int main(void)
{
    cout << "Entering main ..." << endl;
    boost::shared_ptr<X> p1(new X);
    cout << p1.use_count() << endl;
    boost::shared_ptr<X> p2 = p1;
    //boost::shared_ptr<X> p3;
    //p3 = p1;

    cout << p2.use_count() << endl;
    p1.reset();
    cout << p2.use_count() << endl;
    p2.reset();
    cout << "Exiting main ..." << endl;
    return 0;
}
运行结果:

Entering main ...

X ...

1

2

1

~X ...

Exiting main ...

shared_ptr是使用引用计数实现的,当计数值为0的时候,说明原生指针没有任何指针对其进行管理,执行delete。

计数器的变化很简单:当新增一个shared_ptr对原生指针进行管理时,计数值+1,减少一个shared_ptr对原生指针进行管理时,计数值-1.

详解Boost库智能指针(shared_ptr && scoped_ptr && weak_ptr )_第2张图片

上面提到auto_ptr不能放到vector中,但是shared_ptr是可以的。这是因为vectorpush_backconst _Ty:参数是const)是不能修改的(没有特殊处理),但是shared_ptr重载的运算符类型就是const,所以shared_ptr是可以放在STL的容器vector中使用的。另外,由于他的自增是原子性的,也是线程安全的,这在多线程程序中是非常重要的。

boost::shared_ptr并不是绝对安全,下面几条规则能使我们更加安全的使用boost::shared_ptr

1. 避免对shared_ptr所管理的对象的直接内存管理操作,以免造成该对象的重释放

2. shared_ptr并不能对循环引用的对象内存自动管理(这点是其它各种引用计数管理内存方式的通病)。

3. 不要构造一个临时的shared_ptr作为函数的参数。

可以参看http://www.boost.org/doc/libs/1_52_0/libs/smart_ptr/shared_ptr.htm

(三)weak_ptr<T> weak_ptr<T>的引入主要是为了解决shared_ptr的循环引用的问题:

#include <boost/shared_ptr.hpp>
#include <iostream>
using namespace std;

class Parent;
class Child;
typedef boost::shared_ptr<Parent> parent_ptr;
typedef boost::shared_ptr<Child> child_ptr;

class Child
{
public:
    Child()
    {
        cout << "Child ..." << endl;
    }
    ~Child()
    {
        cout << "~Child ..." << endl;
    }
    parent_ptr parent_;
};

class Parent
{
public:
    Parent()
    {
        cout << "Parent ..." << endl;
    }
    ~Parent()
    {
        cout << "~Parent ..." << endl;
    }
    child_ptr child_;
};

int main(void)
{
    parent_ptr parent(new Parent);
    child_ptr child(new Child);
    parent->child_ = child;
    child->parent_ = parent;

    return 0;
}
详解Boost库智能指针(shared_ptr && scoped_ptr && weak_ptr )_第3张图片
如上述程序的例子,运行程序可以发现Child 和 Parent 构造函数各被调用一次,但析构函数都没有被调用。由于Parent和Child对象互相引用,

它们的引用计数最后都是1,不能自动释放,并且此时这两个对象再无法访问到。这就引起了内存泄漏

其中一种解决循环引用问题的办法是 手动打破循环引用,如在return 0; 之前加上一句 parent->child_.reset(),循环引用即被打破。但是手动干预已经违背了我们使用智能指针的初衷。

这里我们使用weak_ptr进行问题解决。这里不再跟踪源码,仅是简单的介绍一下使用,有兴趣的朋友可以自己跟踪一下。

两个常用的功能函数:expired()用于检测所管理的对象是否已经释放;lock()用于获取所管理的对象的强引用智能指针。

对于上面的例子,只要将Parent类的成员换成如下,即可解决循环引用的问题:

class Parent
{
public:
    boost::weak_ptr<parent> child_;
};

强引用与弱引用

强引用,只要有一个引用存在,对象就不能释放

弱引用,并不增加对象的引用计数(实际上是不增加use_count_, 会增加weak_count_);但它能知道对象是否存在

通过weak_ptr访问对象的成员的时候,要提升为shared_ptr

如果存在,提升为shared_ptr(强引用)成功
如果不存在,提升失败
#include <boost/shared_ptr.hpp>
#include <boost/weak_ptr.hpp>
#include <boost/scoped_array.hpp>
#include <boost/scoped_ptr.hpp>
#include <iostream>
using namespace std;

class X
{
public:
    X()
    {
        cout << "X ..." << endl;
    }
    ~X()
    {
        cout << "~X ..." << endl;
    }

    void Fun()
    {
        cout << "Fun ..." << endl;
    }
};
int main(void)
{
    boost::weak_ptr<X> p;
    boost::shared_ptr<X> p3;
    {
        boost::shared_ptr<X> p2(new X);
        cout << p2.use_count() << endl;
        p = p2;
        cout << p2.use_count() << endl;

        /*boost::shared_ptr<X> */
        p3 = p.lock();
        cout << p3.use_count() << endl;
        if (!p3)
            cout << "object is destroyed" << endl;
        else
            p3->Fun();
    }
    /*boost::shared_ptr<X> p4 = p.lock();
    if (!p4)
        cout<<"object is destroyed"<<endl;
    else
        p4->Fun();*/

    if (p.expired())
        cout << "object is destroyed" << endl;
    else
        cout << "object is alived" << endl;

    return 0;
}

X ...

1

1

2

Fun ...

Object is alived

~X ...


从输出可以看出,当p = p2; 时并未增加use_count_,所以p2.use_count() 还是返回1,而从p 提升为 p3,增加了

use_count_, p3.use_count() 返回2;出了大括号,p2 被析构,use_count_ 减为1,程序末尾结束,p3 被析构,

use_count_ 减为0,X 就被析构了。


扩展:

(一)explicit

explicit关键字用来修饰类的构造函数,被修饰的构造函数的类,不能发生相应的隐式类型转换,只能以显示的方式进行类型转换。

Circle A=1.23

实际上进行了以下操作:

Tmp=Cirlce(1.23);

Circle A(tmp);

Tmp.~Circle();

隐式调用了拷贝构造函数,当构造函数都加上explicit关键字后,上面这些都是错误的。

只能是Circle A1.23;(double形参)

Circle C(A);(拷贝构造函数)

(二)const

const常量并非完全不能改变,例如:const int n=100

p=&n;

*p=200;

通过指针来修改是可以的,这是一种技巧。




你可能感兴趣的:(内存泄露,智能指针,shared_ptr,boost,scoped_ptr)