智能指针--shared_ptr&&weak_ptr

来源:互联网 发布:linux新建用户权限 编辑:程序博客网 时间:2024/05/18 03:40

当类中有指针成员时,一般有两种方式来管理指针成员:一是采用值型的方式管理,每个类对象都保留一份指针指向的对象的拷贝;另一种更优雅的方式是使用智能指针,从而实现指针指向的对象的共享。

智能指针(smart pointer)的一种通用实现技术是使用引用计数(reference count)。智能指针类将一个计数器与类指向的对象相关联,引用计数跟踪该类有多少个对象共享同一指针。
       每次创建类的新对象时,初始化指针并将引用计数置为1;当对象作为另一对象的父本而创建时,拷贝构造函数拷贝指针并增加与之相应的引用计数;对一个对象进行赋值时,赋值操作符减少左操作数所指对象的引用计数(如果引用计数为减至0,则删除对象),并增加右操作数所指对象的引用计数;调用析构函数时,析构函数减少引用计数(如果引用计数减至0,则删除基础对象)。
   

       在C++和Android的源代码中,经常会看到形如:sp<xxx>、wp<xxx>这样的类型定义,这其实就是智能指针。智能指针是C++中的一个概念,通过基于引用计数的方法,解决对象的自动释放的问题。在C++编程中,有两个很让人头痛的问题:一是忘记释放动态申请的对象从而造成内存泄露;二是对象在一个地方释放后,又在别的地方被使用,从而引起内存访问错误。程序员往往需要花费很大精力进行精心设计,以避免这些问题的出现。在使用智能指针后,动态申请的内存将会被自动释放(有点类似Java的垃圾回收),不需要再使用delete来释放对象,也不需要考虑一个对象是否已经在其它地方被释放了,从而使程序编写工作减轻不少,而程序的稳定性大大提高。

     boost库里就有定义了智能指针,使用boost::shared_ptr<类名> 指针名来使用智能指针,比如:boost::shared_ptr<SnapCatch> SnapCatch_ptr; 而在Android中,智能指针相关的源代码在下面两个文件中:

    frameworks/base/include/utils/RefBase.h
    frameworks/base/libs/utils/RefBase.cpp

使用sp<类名> 指针名来使用,比如sp<SnapCatch> SnapCatch_ptr;

    智能指针包括两种类型,一种是强指针sp(strong pointer),一种是弱指针(weak pointer)。其实成为强引用和弱引用更合适一些。强指针与一般意义的智能指针概念相同,通过引用计数来记录有多少使用者在使用一个对象,如果所有使用者都放弃了对该对象的引用,则该对象将被自动销毁。

    弱指针也指向一个对象,但是弱指针仅仅记录该对象的地址,不能通过弱指针来访问该对象,也就是说不能通过弱智真来调用对象的成员函数或访问对象的成员变量。要想访问弱指针所指向的对象,需首先将弱指针升级为强指针(通过wp类所提供的promote()方法)。弱指针所指向的对象是有可能在其它地方被销毁的,如果对象已经被销毁,wp的promote()方法将返回空指针,这样就能避免出现地址访问错的情况。

    是不是很神奇?弱指针是怎么做到这一点的呢?其实说穿了一点也不复杂,原因就在于每一个可以被智能指针引用的对象都同时被附加了另外一个weakref_impl类型的对象,这个对象中负责记录对象的强指针引用计数和弱指针引用计数。这个对象是智能指针的实现内部使用的,智能指针的使用者看不到这个对象。弱指针操作的就是这个对象,只有当强引用计数和弱引用计数都为0时,这个对象才会被销毁。

    说了这么多原理,下面该看看到底智能指针该怎么使用了。假设现在有一个类MyClass,如果要使用智能指针来引用这个类的对象,那么这个类需满足下列两个前提条件:

    (1) 这个类是基类RefBase的子类或间接子类;
    (2) 这个类必须定义虚构造函数,即它的构造函数需要这样定义:

          virtual ~MyClass();

    满足了上述条件的类就可以定义智能指针了,定义方法和普通指针类似。比如普通指针是这样定义:

    MyClass* p_obj;

    智能指针是这样定义:

    sp<MyClass> p_obj;

    注意不要定义成 sp<MyClass>* p_obj。初学者容易犯这种错误,这样实际上相当于定义了一个指针的指针。尽管在语法上没有问题,但是最好永远不要使用这样的定义。

    定义了一个智能指针的变量,就可以象普通指针那样使用它,包括赋值、访问对象成员、作为函数的返回值、作为函数的参数等。比如:

    p_obj = new MyClass(); // 注意不要写成 p_obj = new sp<MyClass>

    sp<MyClass> p_obj2 = p_obj;

    p_obj->func();

    p_obj = create_obj();

    some_func(p_obj);

    注意不要试图delete一个智能指针,即 delete p_obj。不要担心对象的销毁问题,智能指针的最大作用就是自动销毁不再使用的对象。不需要再使用一个对象后,直接将指针赋值为NULL即可:

    p_obj = NULL;

    上面说的都是强指针,弱指针的定义方法和强指针类似,但是不能通过弱指针来访问对象的成员。下面是弱指针的示例:

    wp<MyClass> wp_obj = new MyClass();

    p_obj = wp_obj.promote(); // 升级为强指针。不过这里要用.而不是->,真是有负其指针之名啊

    wp_obj = NULL;

   

    智能指针用起来是很方便,在一般情况下最好使用智能指针来代替普通指针。但是需要知道一个智能指针其实是一个对象,而不是一个真正的指针,因此其运行效率是远远比不上普通指针的。所以在对运行效率敏感的地方,最好还是不要使用智能指针为好。


shared_ptr,关键之处在于:当某个类型为shared_ptr的指针,退出作用域时候(即析构函数被调用时候),会检查其引用计数,若为0,则会自动调用所指对象的析构函数(或直接释放内存)。

一个存在疑惑的点是:

1.当智能指针作为类成员变量时候,如何析构?其实问题本质是类成员的也具有析构函数时候,此析构函数何时调用?

2.猜想其实现方式是,多个智能指针指向通过一个引用计数(比如可以采用指向同一个内存地址),实现对引用计数的功能。

3.陈硕的书中,提到智能指针的“计数”功能是原子实现的,究竟是如何个原子法?

1. shared_ptr多次引用同一数据,如下:

{

int* pInt = new int[100];

boost::shared_ptr<int> sp1(pInt);

// 一些其它代码之后…

boost::shared_ptr<int> sp2(pInt);

}

这种情况在实际中是很容易发生的,结果也是非常致命的,它会导致两次释放同一块内存,而破坏堆。 

2. 使用shared_ptr包装this指针带来的问题,如下:

class tester

{

public:

       tester()

       ~tester()

       {

              std::cout << "析构函数被调用!\n";

       }

 

public:

       boost::shared_ptr<tester> sget()

       {

              return boost::shared_ptr<tester>(this);

       }

};

int main()

{

   tester t;

   boost::shared_ptr<tester> sp =  t.sget(); // …

return 0;

}

也将导致两次释放t对象破坏堆栈,一次是出栈时析构,一次就是shared_ptr析构。若有这种需要,可以使用下面代码。

class tester : public boost::enable_shared_from_this<tester>

{

public:

       tester()

       ~tester()

       {

              std::cout << "析构函数被调用!\n";

       }

 

public:

       boost::shared_ptr<tester> sget()

       {

              return shared_from_this();

       }

};

int main()

{

   boost::shared_ptr<tester> sp(new tester);

 // 正确使用sp 指针。

 sp->sget();

 return 0;

}

 

3. shared_ptr循环引用导致内存泄露,代码如下:

class parent;

class child; 

typedef boost::shared_ptr<parent> parent_ptr;

typedef boost::shared_ptr<child> child_ptr; 

class parent

{

public:

       ~parent() {

              std::cout <<"父类析构函数被调用.\n";

       }

 

public:

       child_ptr children;

};

 

class child

{

public:

       ~child() {

              std::cout <<"子类析构函数被调用.\n";

       }

 

public:

       parent_ptr parent;

};

 

int main()

{

     parent_ptr father(new parent());

       child_ptr son(new child);

       // 父子互相引用。

       father->children = son;

       son->parent = father;

    return 0;

}

       如上代码,将在程序退出前,father的引用计数为2,son的计数也为2,退出时,shared_ptr所作操作就是简单的将计数减1,如果为0则释放,显然,这个情况下,引用计数不为0,于是造成father和son所指向的内存得不到释放,导致内存泄露。 

4. 在多线程程序中使用shared_ptr应注意的问题。代码如下:

class tester

{

public:

   tester() {}

   ~tester() {}

// 更多的函数定义…

};

 

void fun(boost::shared_ptr<tester> sp)

{

    // !!!在这大量使用sp指针.

  boost::shared_ptr<tester> tmp = sp;

}

 

int main()

{

    boost::shared_ptr<tester> sp1(new tester);

    // 开启两个线程,并将智能指针传入使用。

                    boost::thread t1(boost::bind(&fun, sp1));

                       boost::thread t2(boost::bind(&fun, sp1));

 

                       t1.join();

      t2.join();

    return 0;

}

   这个代码带来的问题很显然,由于多线程同是访问智能指针,并将其赋值到其它同类智能指针时,很可能发生两个线程同时在操作引用计数(但并不一定绝对发生),而导致计数失败或无效等情况,从而导致程序崩溃,如若不知根源,就无法查找这个bug,那就只能向上帝祈祷程序能正常运行。

可能一般情况下并不会写出上面这样的代码,但是下面这种代码与上面的代码同样,如下:

class tester

{

public:

   tester() {}

   ~tester() {}

public:

   boost::shared_ptr<int> m_spData; // 可能其它类型。

};

tester gObject;

void fun(void)

{

    // !!!在这大量使用sp指针.

  boost::shared_ptr<int> tmp = gObject.m_spData;

}

 

int main()

{

   // 多线程。

                    boost::thread t1(&fun);

                       boost::thread t2(&fun);

 

                       t1.join();

      t2.join();

    return 0;

}

情况是一样的。要解决这类问题的办法也很简单,使用boost.weak_ptr就可以很方便解决这个问题。第一种情况修改代码如下:

class tester

{

public:

   tester() {}

   ~tester() {}

// 更多的函数定义…

};

 

void fun(boost::weak_ptr<tester> wp)

{

  boost::shared_ptr<tester> sp = wp.lock;

  if (sp)

{

     // 在这里可以安全的使用sp指针.

}

else

{

   std::cout << “指针已被释放!” << std::endl;

}

int main()

{

    boost::shared_ptr<tester> sp1(new tester);

    boost.weak_ptr<tester> wp(sp1);

    // 开启两个线程,并将智能指针传入使用。

                    boost::thread t1(boost::bind(&fun, wp));

                       boost::thread t2(boost::bind(&fun, wp));

 

                       t1.join();

      t2.join();

    return 0;

}

       boost.weak_ptr指针功能一点都不weak,weak_ptr是一种可构造、可赋值以不增加引用计数来管理shared_ptr的指针,它可以方便的转回到shared_ptr指针,使用weak_ptr.lock函数就可以得到一个shared_ptr的指针,如果该指针已经被其它地方释放,它则返回一个空的shared_ptr,也可以使用weak_ptr.expired()来判断一个指针是否被释放。

boost.weak_ptr不仅可以解决多线程访问带来的安全问题,而且还可以解决上面第三个问题循环引用。Children类代码修改如下,即可打破循环引用:

class child

{

public:

       ~child() {

              std::cout <<"子类析构函数被调用.\n";

       }

 

public:

       boost::weak_ptr<parent> parent;

};

因为boost::weak_ptr不增加引用计数,所以可以在退出函数域时,正确的析构。

原创粉丝点击