C++11智能指针之std::shared_ptr



        std::shared_ptr是在c++11中引入的一种智能指针,其特点是它所指向的资源具有共享性,即多个shared_ptr可以指向同一份资源。在c++中使用shared_ptr需要包含<memory>头文件。

一、定义并初始化一个共享指针的三种方式

class Investment
{
public:
      virtual void getObjectType() { cout<<"Base Investment"<< endl; }
      virtual ~Investment() { cout<<"Investmentdestruction!"<< endl; }
};

1通过new运算符或者普通指针

shared_ptr<Investment> sp(newInvestment());

Investment * pInvestment = new Investment();

shared_ptr<Investment> sp (pInvestment);

2通过make_shared方法

auto sp1 = std::make_shared<Investment>();

3通过拷贝另一个智能指针

shared_ptr<Investment> sp2(sp);
//或者
shared_ptr<Investment> sp3 = sp2;

二、shared_ptr的结构

       相比于普通指针,共享指针要占用多一倍的内存空间,其内部包含两个指针,一个指针指向它所管理的资源,第二个指针指向一个称为“Control Block”的控制块,如图所示:

       通过上图所示的结构,我们可以知道在控制块中有一个引用计数字段(Reference Count),用来记录指向Object的共享指针数量,当我们声明并初始化一个共享指针时,Reference Count的值为1,当有另一个共享指针指向该Object时(比如通过赋值或拷贝构造函数,将一个共享指针赋值给另一共享指针),Reference count 的值递增1

      第二个字段:Weak Count,也是一个引用计数,它用来计数指向该Objectstd::weak_ptr指针的数量,关于std::weak_ptr我们将在下一章节讨论。

      引用计数的存在使得std::shared_ptr有一些比较特别的特性:

  1. Std::shared_ptr的大小两倍于普通指针。

  2. 控制块必须是动态分配的,这是由于被shared_ptr所管理的对象并不知道有“引用计数”的存在,所以在被管理对象中并没有空间来存储这个引用计数。

  3. 引用计数的增减必须是原子性的,否则在多线程环境下,由于线程竞争的存在,会导致对Reference count的脏读、脏写。

三、资源释放

    shared_ptr默认情况下使用delete释放资源,但是用户也可以指定自己的资源释放函数,例如在下面这个例子中,pInvestdelete_Investment进行资源释放操作:

auto delete_Investment = [](Investment*pInv)
{
      pInv->getObjectType();
      delete pInv;
};

shared_ptr<Investment> pInvest(new Investment(),delete_Investment)

   资源释放器的地址存放于控制块(Control Block)中,因此增加用户自定义的资源释放器不会增加shared_ptr的大小,其大小仍然为两个指针的大小。

四、关于控制块(Control Block)的创建

   当代码第一次为一个对象创建shared_ptr指针指向这个对象的时候,控制块就会被创建,或者说它应该被创建。但是问题在于,一处代码在创建shared_ptr来指向一个对象时,这处代码并不知道它创建的这个shared_ptr所要指向的对象是否已经有别的shared_ptr指向了,即这个对象是否已经有一个控制块与之对应了。为了解决这个问题,控制块的创建遵循以下原则:

  1. std::make_shared总是会创建一个控制块。这是因为当调用std::make_shared创建智能指针的时候,智能指针所要指向的这个对象是第一次被创建的。

  2. 当通过一个普通指针作为参数构建一个智能指针的时候,会为这个指针所指向的对象创建一个控制块。

    在这种情况下,如果用户想要为一个已经有控制块的对象创建一个shared_ptr时,需要用shared_ptr或者weak_ptr作为共享指针构造函数的参数,而不能用普通指针作为参数。

   注意:使用普通指针作为参数构造一个shared_ptr有可能会导致多个控制块(control block)的创建。这种情形并不总是会很容易的被意识到,看下面的例子:

   假设投资类中有一个函数来处理投资,一个vector来追踪被处理过的投资,代码如下:

std::vector<shared_ptr<Investment>> m_ptrVec;

class Investment
{
public:
      void Handler(); // handle the investment 
};

void Investment::Handler()
{
      m_ptrVec.emplace_back(this);
}

   在投资处理函数Handler中,用this作为参数传给vectoremplace_back函数,但这样做其实蕴藏了风险,试想如果在代码中的其他地方有一个shared_ptr也指向了这个投资对象,这样就会有两个控制块被建立起来。那么,有没有一种办法,让我们可以使用this指针构造shared_ptr,但是又不会引起控制块的重复创建呢?

   幸运的是,在STL中有一个模板类正是为此而生,它就是std::enable_shared_from_this。代码要达到这个目的,必须继承std::enable_shared_from_this类,并将this指针用shared_from_this()接口代替,就像下面的代码一样:

 

class Investment :public std::enable_shared_from_this<Investment>
{
public:
      void Handler();
};

void Investment::Handler()
{
      m_ptrVec.emplace_back(shared_from_this());
}

shared_from_this()会查询当前对象的控制块,并创建一个新的shared_ptr关联到这个控制块。如果这个对象还没有与之对应的控制块,shared_from_this()会抛出异常。

五、最后一点:

C++17 以前,shared_ptr并不支持动态数组,没有std::shared_ptr<T[]>.所以shared_ptr只能管理单个对象,而不能管理对象数组。

C++17及以上,增加了shared_ptr 对动态数组的支持。比如,向下面这样:

std::shared_ptr<int[]> p(new int[10]);

  • 7
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值