shared_ptr理解

来源:互联网 发布:c语言表白小程序代码 编辑:程序博客网 时间:2024/06/08 16:09

shared_ptr是一种智能指针(smart pointer)。shared_ptr的作用有如同指针,但会记录有多少个shared_ptrs共同指向一个对象。

shared_ptr是C++非常重要的一个防止内存泄露的设计

作用:

这便是所谓的引用计数(reference counting)。一旦最后一个这样的指针被销毁,也就是一旦某个对象的引用计数变为0,这个对象会被自动删除。这在非环形数据结构中防止资源泄露很有帮助。
auto_ptr由于它的破坏性复制语义,无法满足标准容器对元素的要求,因而不能放在标准容器中;如果我们希望当容器析构时能自动把它容纳的指针元素所指的对象删除时,通常采用一些间接的方式来实现,显得比较繁琐。boost库中提供了一种新型的智能指针shared_ptr,它解决了在多个指针间共享对象所有权的问题,同时也满足容器对元素的要求,因而可以安全地放入容器中。
用法:

1.1删除共享对象

使用shared_ptr解决的主要问题是知道删除一个被多个客户共享的资源的正确时机。下面是一个简单易懂的例子,有两个类 A和 B, 它们共享一个int实例。使用 boost::shared_ptr, 你必须包含"boost/shared_ptr.hpp".
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#include "boost/shared_ptr.hpp"
#include <cassert>
class A {
    boost::shared_ptr<int> no_;
public:
    A(boost::shared_ptr<int> no) : no_(no) {}
    void value(int i) {
        *no_=i;
    }
};
class B {
    boost::shared_ptr<int> no_;
public:
    B(boost::shared_ptr<int> no) : no_(no) {}
    int value() const {
        return *no_;
    }
};
int main() {
    boost::shared_ptr<int> temp(new int(14));
    A a(temp);
    B b(temp);
    a.value(28);
    assert(b.value()==28);
类 A和 B都保存了一个 shared_ptr<int>. 在创建 A和 B的实例时,shared_ptr temp被传送到它们的构造函数。这意味着共有三个 shared_ptr:a, b, 和 temp,它们都引向同一个int实例。如果我们用指针来实现对一个的共享,A和 B必须能够在某个时间指出这个int要被删除。在这个例子中,直到main的结束,引用计数为3,当所有 shared_ptr离开了作用域,计数将达到0,而最后一个智能指针将负责删除共享的 int.

1.2标准容器

把对象直接存入容器中有时会有些麻烦。以值的方式保存对象意味着使用者将获得容器中的元素的拷贝,对于那些复制是一种昂贵的操作的类型来说可能会有性能的问题。此外,有些容器,特别是 std::vector, 当你加入元素时可能会复制所有元素,这更加重了性能的问题。最后,传值的语义意味着没有多态的行为。如果你需要在容器中存放多态的对象而且你不想切割它们,你必须用指针。如果你用裸指针,维护元素的完整性会非常复杂。从容器中删除元素时,你必须知道容器的使用者是否还在引用那些要删除的元素,不用担心多个使用者使用同一个元素。这些问题都可以用shared_ptr来解决。
下面是如何把共享指针存入标准库容器的例子。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#include "boost/shared_ptr.hpp"
#include <vector>
#include <iostream>
class A {
public:
    virtual void sing()=0;
    protected:
    virtual ~A() {};
};
class B : public A {
public:
    virtual void sing() {
        std::cout << "Do re mi fa so la";
    }
};
boost::shared_ptr<A> createA() {
    boost::shared_ptr<A> p(new B());
    return p;
}
int main() {
    typedef std::vector<boost::shared_ptr<A> > container_type;
    typedef container_type::iterator iterator;
    container_type container;
    for (int i=0;i<10;++i) {
        container.push_back(createA());
    }
    std::cout << "The choir is gathered: \n";
    iterator end=container.end();
    for (iterator it=container.begin();it!=end;++it) {
        (*it)->sing();
    }
这里有两个类, A和 B, 各有一个虚拟成员函数 sing. B从 A公有继承而来,并且如你所见,工厂函数 createA返回一个动态分配的B的实例,包装在shared_ptr<A>里。在 main里, 一个包含shared_ptr<A>的 std::vector被放入10个元素,最后对每个元素调用sing。如果我们用裸指针作为元素,那些对象需要被手工删除。而在这个例子里,删除是自动的,因为在vector的生存期中,每个shared_ptr的引用计数都保持为1;当 vector被销毁,所有引用计数器都将变为零,所有对象都被删除。有趣的是,即使 A的析构函数没有声明为 virtual, shared_ptr也会正确调用 B的析构函数!
上面的例子示范了一个强有力的技术,它涉及A里面的protected析构函数。因为函数 createA返回的是 shared_ptr<A>, 因此不可能对shared_ptr::get返回的指针调用 delete。这意味着如果为了向某个需要裸指针的函数传送裸指针而从shared_ptr中取出裸指针的话,它不会由于意外地被删除而导致灾难。那么,又是如何允许 shared_ptr删除它的对象的呢? 这是因为指针指向的真正类型是 B; 而B的析构函数不是protected的。这是非常有用的方法,用于给shared_ptr中的对象增加额外的安全性
RAII

现在考虑如下一个资源从创建到销毁的过程:
{
int* pInt = new int(14);
...... // 此处有一系列代码
delete pInt;
}
  如果程序在......这些代码的执行中跳出了异常。这样导致程序不会运行到delete pInt2这里,这时资源泄露了。

  如何防止这种情况呢? 由此我们联想到类的构造和和类离开作用域时的自动析构.这就是资源获取即初始化技术。资源获取即初始化(RAII, Resource Acquisition Is Initialization)是指,当你

获得一个资源的时候,不管这个资源是对象、内存、文件句柄或者其它什么,你都要在一个对象的构造函数中获得它, 并且在该对象的析构函数中释放它。实现这种功能的类,我们就说它采用了"资源

获取即初始化(RAII)"的方式。这样的类常常被称为封装类。

下面为一个最简单的RAII:

class CMySingleLock 
{
public:
CMySingleLock()
{
     InitializeCriticalSection(&m_CritSec);
     EnterCriticalSection(&m_CritSec);
}

~CMySingleLock (void) 
{
     LeaveCriticalSection(&m_CritSec);
     DeleteCriticalSection(&m_CritSec);
}

private:
CRITICAL_SECTION     m_CritSec;
};

这样就可以这样调用:

{

CMySingleLock mySingleLock;

....处理变量

} // 此处mySingleLock已经离开作用域,自动解锁

  很多程序用到了该技术,如mutex, criticalSection. 这样,当你创建资源的时候就立即将它放入封装类对象构造函数(new出来的指针立即放入shared_ptr析造函数里), 当该对象离开作用域时,

对象析构函数会自动销毁资源(shared_ptr对象离开作用域时,会自动销毁指向的资源).

1.2
  shared_ptr是典型的资源获取即初始化技术。不过shared_ptr采用了更复杂一点RAII方式,因为它实现了引用计数。当创建资源的时候将它放入一个shared_ptr, shared_ptr内部记录对这种资源的引用次数为1次。当这个shared_ptr对象离开作用域时,引用次数减1,shared_ptr对象析构时,检查到引用次数为0了,就销毁资源:

{
boost::shared_ptr pInt(new int(14));
assert(pInt.use_count() == 1); // new int(14)这个指针被引用1次
...... // 此处有一系列代码

}    //pInt离开作用域, 所以new int(14)被引用次数为0. 指针被销毁。防止了

  此处及以下assert代码都会判断成功。如果......跳出异常。那么pInt也离开了作用域,指针照常还是被销毁,所以智能指针可以有效防止资源泄露。

考虑更多次的引用情况:

{

boost::shared_ptr pInt2;
assert(pInt2.use_count() == 0);   // temp2还没有引用指针

{

    boost::shared_ptr pInt1(new int(14));
    assert(pInt1.use_count() == 1); // new int(14)这个指针被引用1次

    pInt2 = pInt1;
    assert(pInt1.use_count() == 2); // new int(14)这个指针被引用2次
    assert(pInt2.use_count() == 2);
} //pInt1离开作用域, 所以new int(14)被引用次数-1

assert(pInt2.use_count() == 1);
} // pInt2离开作用域,引用次数-1,现在new int(14)被引用0次,所以销毁它

不管资源曾经被多少次引用。当它被引用0次时就会销毁。

1.3

  在shard_ptr使用中经常会发现,一个对象会有两次被析构的情况。其实这种是因为那个对象指针被两次当成shard_ptr构造函数里的参数。一定要避免这种现象。考虑如下代码:

{
int* pInt = new int(14);
boost::shared_ptr temp1(pInt);
assert(temp1.use_count() == 1);      // 用一个指针初始化temp1,temp1认为pInt只被它拥有。所以这个指针被引用1次

boost::shared_ptr temp2(pInt); // 用一个指针初始化temp2,temp2认为pInt只被它拥有。所以这个指针被引用1次
assert(temp2.use_count() == 1);

}   // temp1,temp2都离开作用域,它们都销毁pInt. pInt被销毁了两次!系统终于崩溃了 -_-


正确的做法是将原始指针赋给智能指针后,以后的操作都要针对智能指针了.

{
boost::shared_ptr temp1(new int(14)); // 资源获取即初始化
assert(temp1.use_count() == 1);

boost::shared_ptr temp2(temp1);
assert(temp2.use_count() == 2);

}   // temp1,temp2都离开作用域,引用次数变为0,指针被销毁

1.4
如果资源的创建销毁不是以new,delete的方式创建销毁怎么办?shared_ptr也可以指定删除器:

// FileCloser.h FileCloser删除器  
class FileCloser
{
public:
void operator()(FILE *pf)
{
    if (pf)
    {
      fclose(pf);
    }
}
};

// 某实现文件
{
boost::shared_ptr fp(fopen(pszConfigFile, "r"), FileCloser());    // 指定调用FileCloser函数对象销毁资源
}


1.5
  shared_ptr已经被即将到来的标准库技术报告所采纳,将成为tr1中的一员。为了以后更好的移植现有代码到C++新标准中。可以使用一个namespace的一个小技巧,在头文件StdAfx.h中声明(参考

Effective C++ Item 54):
namespace std

{

namespace tr1 = ::boost;           // namespace std::tr1 is an alias

}                                   // for namespace boost

这样就可以用如下方法写代码:

std::tr1::shared_ptr pInt(new int(14));



0 0
原创粉丝点击