热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

C++boost库中的智能指针介绍

简单地说,智能指针的作用就是在对象已经不再被需要的时候,自动释放掉它,原理不外乎就是利用构造、拷贝构造、赋值、析构这几个东西来进行的,       智能指针能够使C

简单地说,智能指针的作用就是在对象已经不再被需要的时候,自动释放掉它,
原理不外乎就是利用构造、拷贝构造、赋值、析构这几个东西来进行的,
 
 
         智能指针能够使C++的开发简单化,主要是它能够像其它限制性语言(如C#、VB)自动管理内存的释放,而且能够做更多的事情。

 
1、 什么是智能指针
智能指针是一种像指针的C++对象,但它能够在对象不使用的时候自己销毁掉。
我们知道在C++中的对象不再使用是很难定义的,因此C++中的资源管理是很复杂的。各种智能指针能够操作不同的情况。当然,智能指针能够在任务结束的时候删除对象,除了在程序之外。
许多库都提供了智能指针的操作,但都有自己的优点和缺点。Boost库是一个高质量的开源的C++模板库,很多人都考虑将其加入下一个C++标准库的版本中。
 
Boost提供了下面几种智能指针:
 
shared_ptr
本指针中有一个引用指针记数器,表示类型T的对象是否已经不再使用。shared_ptr 是Boost中提供普通的智能指针,大多数地方都使用shared_ptr。
scoped_ptr
当离开作用域能够自动释放的指针。因为它是不传递所有权的。事实上它明确禁止任何想要这样做的企图!这在你需要确保指针任何时候只有一个拥有者时的任何一种情境下都是非常重要的。
intrusive_ptr
比 shared_ptr 更好的智能指针,但是需要类型 T 提供自己的指针使用引用记数机制。
weak_ptr
一个弱指针,帮助shared_ptr 避免循环引用。
shared_array
和 shared_ptr 类似,用来处理数组的。
scoped_array
和 scoped_ptr 类似,用类处理数组的。
 
 
下面让我们看一个简单的例子:
 
2、 首先介绍:boost::scoped_ptr
scoped_ptr 是 Boost 提供的一个简单的智能指针,它能够保证在离开作用域后对象被释放。
例子说明:本例子使用了一个帮助我们理解的类: CSample, 在类的构造函数、赋值函数、析构函数中都加入了打印调试语句。因此在程序执行的每一步都会打印调试信息。在例子的目录里已经包含了程序中需要的Boost库的部分内容,不需要下载其它内容(查看Boost的安装指南)。
 
下面的例子就是使用scoped_ptr 指针来自动释放对象的:
 
使用普通指针
使用scoped_ptr 指针
void Sample1_Plain()
{
  CSample * pSample(new CSample);
 
  if (!pSample->Query() )
  // just some function...
  {
    delete pSample;
    return;
  }
 
  pSample->Use();
  delete pSample;
}
#include "boost/smart_ptr.h"
 
void Sample1_ScopedPtr()
{
  boost::scoped_ptr
       samplePtr(new CSample);
 
  if (!samplePtr->Query() )
  // just some function...
    return;   
 
  samplePtr->Use();
 
}
 
使用普通普通指针的时候,我们必须记住在函数退出的时候要释放在这个函数内创建的对象。当我们使用例外的时候处理指针是特别烦人的事情(容易忘记销毁它)。使用scoped_ptr 指针就能够在函数结束的时候自动销毁它,但对于函数外创建的指针就无能为力了。
优点:对于在复杂的函数种,使用scoped_ptr 指针能够帮助我们处理那些容易忘记释放的对象。也因此在调试模式下如果使用了空指针,就会出现一个断言。
 
 
优点
自动释放本地对象和成员变量[1],延迟实例化,操作PIMPL和RAII(看下面)
缺点
在STL容器里,多个指针操作一个对象的时候需要注意。
性能
使用scoped_ptr 指针,会增加一个普通指针。
 
 
3、 引用指针计数器
引用指针计数器记录有多少个引用指针指向同一个对象,如果最后一个引用指针被销毁的时候,那么就销毁对象本身。
shared_ptr 就是Boost中普通的引用指针计数器,它表示可以有多个指针指向同一个对象,看下面的例子:
 
void Sample2_Shared()
{
  // (A) 创建Csample类的一个实例和一个引用。
  boost::shared_ptr mySample(new CSample);
  printf("The Sample now has %i references\n", mySample.use_count()); // The Sample now has 1 references
  // (B) 付第二个指针给它。
  boost::shared_ptr mySample2 = mySample; // 现在是两个引用指针。
  printf("The Sample now has %i references\n", mySample.use_count());
 
  // (C) 设置第一个指针为空。
  mySample.reset();
  printf("The Sample now has %i references\n", mySample2.use_count());  // 一个引用
 
  // 当mySample2离开作用域的时候,对象只有一个引用的时候自动被删除。
}
 
 
在(A)中在堆栈重创建了CSample类的一个实例,并且分配了一个shared_ptr指针。对象mySample入下图所示:

然后我们分配了第二个指针mySample2,现在有两个指针访问同一个数据。
 
我们重置第一个指针(将mySample设置为空),程序中仍然有一个Csample实例,mySample2有一个引用指针。
 
只要当最有一个引用指针mySample2退出了它的作用域之外,Csample这个实例才被销毁。
 
当然,并不仅限于单个Csample这个实例,或者是两个指针,一个函数,下面是用shared_ptr的实例:
·         用作容器中。
·         用在PIMPL的惯用手法 (the pointer-to-implementation idiom )。
·         RAII(Resource-Acquisition-Is-Initialization)的惯用手法中。
·         执行分割接口。
注意:如果你没有听说过PIMPL (a.k.a. handle/body) 和 RAII,可以找一个好的C++书,在C++中处于重要的内容,一般C++程序员都应该知道(不过我就是第一次看到这个写法)。智能指针只是一中方便的他们的方法,本文中不讨论他们的内容。
PIMPL:如果必须包容一个可能抛异常的子对象,但仍然不想从你自己的构造函数中抛出异常,考虑使用被叫做Handle Class或Pimpl的方法(“Pimpl”个双关语:pImpl或“pointer to implementation”)
 
4、 主要特点
boost::shared_ptr 有一些重要的特征必须建立在其它操作之上。
·         shared_ptr作用在一个未知类型上
当声明或定义一个shared_ptr,T可能是一个未知的类型。例如你仅仅在前面声明了class T,但并没有定义class T。当我们要释放这个指针的时候我们需要知道这个T具体是一个声明类型。
·         shared_ptr作用在任意类型上
在这里本质上不需要制定T的类型(如从一个基类继承下来的)
·         shared_ptr支持自己定义释放对象的操作
如果你的类中自己写了释放方法,也可以使用。具体参照Boost文档。
·         强制转换
如果你定义了一个U*能够强制转换到T*(因为T是U的基类),那么shared_ptr也能够强制转换到shared_ptr
·         shared_ptr 是线程安全的
(这种设计的选择超过它的优点,在多线程情况下是非常必要的)
·         已经作为一种惯例,用在很多平台上,被证明和认同的。
 
5、 例子:在容器中使用shared_ptr
许多容器类,包括STL,都需要拷贝操作(例如,我们插入一个存在的元素到list,vector,或者container。)当拷贝操作是非常销毁资源的时候(这些操作时必须的),典型的操作就是使用容器指针。
 
std::vector vec;
vec.push_back( new CMyLargeClass("bigString") );
 
 
将内存管理的任务抛给调用者,我们能够使用shared_ptr来实现。
 
typedef boost::shared_ptr  CMyLargeClassPtr;
std::vector vec;
vec.push_back( CMyLargeClassPtr(new CMyLargeClass("bigString")) );
 
 
当vector被销毁的时候,这个元素自动被销毁了。当然,除非有另一个智能指针引用了它,则还本能被销毁。让我们看Sample3中的使用:
 
void Sample3_Container()
{
  typedef boost::shared_ptr CSamplePtr;
 
  // (A) create a container of CSample pointers:
  std::vector vec;
 
  // (B) add three elements
  vec.push_back(CSamplePtr(new CSample));
  vec.push_back(CSamplePtr(new CSample));
  vec.push_back(CSamplePtr(new CSample));
 
  // (C) "keep" a pointer to the second:
  CSamplePtr anElement = vec[1];
 
  // (D) destroy the vector:
  vec.clear();
 
  // (E) the second element still exists
  anElement->Use();
  printf("done. cleanup is automatic\n");
 
  // (F) anElement goes out of scope, deleting the last CSample instance
}
 
 
6、 使用Boost中的智能指针,什么是正确的使用方法
使用智能指针的一些操作会产生错误(突出的事那些不可用的引用计数器,一些对象太容易释放,或者根本释放不掉)。Boost增强了这种安全性,处理了所有潜在存在的危险,所以我们要遵循以下几条规则使我们的代码更加安全。
下面几条规则是你应该必须遵守的:
规则一:赋值和保存 —— 对于智能指针来说,赋值是立即创建一个实例,并且保存在那里。现在智能指针拥有一个对象,你不能手动释放它,或者取走它,这将帮助你避免意外地释放了一个对象,但你还在引用它,或者结束一个不可用的引用计数器。
规则二:_ptr 不是T* —— 恰当地说,不能盲目地将一个T* 和一个智能指针类型T相互转换。意思是:
·         当创建一个智能指针的时候需要明确写出 __ptr myPtr
·         不能将T*赋值给一个智能指针。
·         不能写ptr = NULL,应该使用ptr.reset()。
·         重新找回原始指针,使用ptr.get(),不必释放这个指针,智能指针会去释放、重置、赋值。使用get()仅仅通过函数指针来获取原始指针。
·         不能通过T*指向函数指针来代表一个__ptr,需要明确构造一个智能指针,或者说将一个原始指针的所有权给一个指针指针。(见规则三)
·         这是一种特殊的方法来认定这个智能指针拥有的原始指针。不过在Boost:smart pointer programming techniques 举例说明了许多通用的情况。
规则三:非循环引用 —— 如果有两个对象引用,而他们彼此都通过一个一个引用指针计数器,那么它们不能释放,Boost 提供了weak_ptr来打破这种循环引用(下面介绍)。
规则四:非临时的 share_ptr —— 不能够造一个临时的share_ptr来指向它们的函数,应该命名一个局部变量来实现。(这可以使处理以外更安全,Boost share_ptr best practices 有详细解说)。
7、 循环引用
引用计数器是一种便利的资源管理机制,它有一个基本回收机制。但循环引用不能够自动回收,计算机很难检测到。一个最简单的例子,如下:
 
struct CDad;
struct CChild;
 
typedef boost::shared_ptr   CDadPtr;
typedef boost::shared_ptr  CChildPtr;
 
struct CDad : public CSample
{
   CChildPtr myBoy;
};
 
struct CChild : public CSample
{
 CDadPtr myDad;
};
 
// a "thing" that holds a smart pointer to another "thing":
 
CDadPtr   parent(new CDadPtr);
CChildPtr child(new CChildPtr);
 
// deliberately create a circular reference:
parent->myBoy = child;
child->myDad = dad;
 
// resetting one ptr...
child.reset();
 
         parent 仍然引用CDad对象,它自己本身又引用CChild。整个情况如下图所示:
如果我们调用dad.reset(),那么我们两个对象都会失去联系。但这种正确的离开这个引用,共享的指针看上去没有理由去释放那两个对象,我们不能够再访问那两个对象,但那两个对象的确还存在,这是一种非常严重的内存泄露。如果拥有更多的这种对象,那么将由更多的临界资源不能正常释放。
       如果不能解决好共享智能指针的这种操作,这将是一个严重的问题(至少是我们不可接受的)。因此我们需要打破这种循环引用,下面有三种方法:
A、   当只剩下最后一个引用的时候需要手动打破循环引用释放对象。
B、   当Dad的生存期超过Child的生存期的时候,Child需要一个普通指针指向Dad。
C、  使用boost::weak_ptr打破这种循环引用。
方法A和B并不是一个完美的解决方案,但是可以在不使用weak_ptr的情况下让我们使用智能指针,让我们看看weak_ptr的详细情况。
 
8、 使用weak_ptr跳出循环
强引用和弱引用的比较:
一个强引用当被引用的对象活着的话,这个引用也存在(就是说,当至少有一个强引用,那么这个对象就不能被释放)。boost::share_ptr就是强引用。相对而言,弱引用当引用的对象活着的时候不一定存在。仅仅是当它存在的时候的一个引用。
boost::weak_ptr 是执行弱引用的智能指针。当你需要它的时候就可以使用一个强(共享)指针指向它(当对象被释放的时候,它为空),当然这个强指针在使用完毕应该立即释放掉,在上面的例子中我们能够修改它为弱指针。
 
struct CBetterChild : public CSample
{
  weak_ptr myDad;
 
  void BringBeer()
  {
    shared_ptr strOngDad= myDad.lock(); // request a strong pointer
    if (strongDad)                      // is the object still alive?
      strongDad->SetBeer();
    // strongDad is released when it goes out of scope.
    // the object retains the weak pointer
  }
};
 
 
9、 Intrusive_ptr——轻量级共享智能指针
shared_ptr比普通指针提供了更完善的功能。有一个小小的代价,那就是一个共享指针比普通指针占用更多的空间,每一个对象都有一个共享指针,这个指针有引用计数器以便于释放。但对于大多数实际情况,这些都是可以忽略不计的。
intrusive_ptr 提供了一个折中的解决方案。它提供了一个轻量级的引用计数器,但必须对象本身已经有了一个对象引用计数器。这并不是坏的想法,当你自己的设计的类中实现智能指针相同的工作,那么一定已经定义了一个引用计数器,这样只需要更少的内存,而且可以提高执行性能。
如果你要使用intrusive_ptr 指向类型T,那么你就需要定义两个函数:intrusive_ptr_add_ref 和intrusive_ptr_release。下面是一个简单的例子解释如何在自己的类中实现:
 
#include "boost/intrusive_ptr.hpp"
 
// forward declarations
class CRefCounted;
 
 
namespace boost
{
    void intrusive_ptr_add_ref(CRefCounted * p);
    void intrusive_ptr_release(CRefCounted * p);
};
 
// My Class
class CRefCounted
{
  private:
    long    references;
    friend void ::boost::intrusive_ptr_add_ref(CRefCounted * p);
    friend void ::boost::intrusive_ptr_release(CRefCounted * p);
 
  public:
    CRefCounted() : references(0) {}   // initialize references to 0
};
 
// class specific addref/release implementation
// the two function overloads must be in the boost namespace on most compilers:
namespace boost
{
 inline void intrusive_ptr_add_ref(CRefCounted * p)
  {
    // increment reference count of object *p
    ++(p->references);
  }
 
 
 
 inline void intrusive_ptr_release(CRefCounted * p)
  {
   // decrement reference count, and delete object when reference count reaches 0
   if (--(p->references) == 0)
     delete p;
  }
} // namespace boost
 
        
         这是一个最简单的(非线程安全)实现操作。但作为一种通用的操作,如果提供一种基类来完成这种操作或许很有使用价值,也许在其他地方会介绍到。
 
10、 scoped_array 和 shared_array
scoped_array 和 shared_array和上面讲的基本上相同,只不过他们是指向数组的。就像使用指针操作一样使用operator new[] ,他们都重载了operator new[]。注意他们并不初始化分配长度。

C++ boost库中的智能指针介绍,布布扣,bubuko.com


推荐阅读
  • 利用无代码平台实现高效业务应用开发
    随着市场环境的变化加速,全球企业都在探索更为敏捷的应用开发模式,以便快速响应新兴的商业机遇。然而,传统的软件开发方式不仅成本高昂,而且耗时较长,这往往导致IT与业务部门之间的合作障碍,进而影响项目的成功。本文将探讨如何通过无代码开发平台解决这些问题。 ... [详细]
  • 本文探讨了程序员这一职业的本质,认为他们是专注于问题解决的专业人士。文章深入分析了他们的日常工作状态、个人品质以及面对挑战时的态度,强调了编程不仅是一项技术活动,更是个人成长和精神修炼的过程。 ... [详细]
  • 在日常生活中,支付宝已成为不可或缺的支付工具之一。本文将详细介绍如何通过支付宝实现免费提现,帮助用户更好地管理个人财务,避免不必要的手续费支出。 ... [详细]
  • 我的读书清单(持续更新)201705311.《一千零一夜》2006(四五年级)2.《中华上下五千年》2008(初一)3.《鲁滨孙漂流记》2008(初二)4.《钢铁是怎样炼成的》20 ... [详细]
  • Windows操作系统提供了Encrypting File System (EFS)作为内置的数据加密工具,特别适用于对NTFS分区上的文件和文件夹进行加密处理。本文将详细介绍如何使用EFS加密文件夹,以及加密过程中的注意事项。 ... [详细]
  • 在项目冲刺的最后一天,团队专注于软件用户界面的细节优化,包括调整控件布局和字体设置,以确保界面的简洁性和用户友好性。 ... [详细]
  • JavaScript 页面卸载事件详解 (onunload)
    当用户从页面离开时(如关闭页面或刷新页面),会触发 onunload 事件,此时可以执行预设的脚本。需要注意的是,不同的浏览器对 onunload 事件的支持程度可能有所不同。 ... [详细]
  • 默认情况下,Git 使用 Nano 编辑器进行提交信息的编辑,但如果您更喜欢使用 Vim,可以通过简单的配置更改来实现这一变化。本文将指导您如何通过修改全局配置文件来设置 Vim 作为默认的 Git 提交编辑器。 ... [详细]
  • 探索Java 11中的ZGC垃圾收集器
    Java 11引入了一种新的垃圾收集器——ZGC,由Oracle公司研发,旨在支持TB级别的内存容量,并保证极低的暂停时间。本文将探讨ZGC的开发背景、技术特点及其潜在的应用前景。 ... [详细]
  • 本文探讨了使用普通生成函数和指数生成函数解决组合与排列问题的方法,特别是在处理特定路径计数问题时的应用。文章通过详细分析和代码实现,展示了如何高效地计算在给定条件下不相邻相同元素的排列数量。 ... [详细]
  • 在Notepad++中配置Markdown语法高亮及实时预览功能
    本文详细介绍了如何在Notepad++中配置Markdown语法高亮和实时预览功能,包括必要的插件安装和设置步骤。 ... [详细]
  • 探讨如何在映射文件中处理重复的属性字段,以避免数据操作时出现错误。 ... [详细]
  • 网络流24题——试题库问题
    题目描述:假设一个试题库中有n道试题。每道试题都标明了所属类别。同一道题可能有多个类别属性。现要从题库中抽取m道题组成试卷。并要求试卷包含指定类型的试题。试设计一个满足要求的组卷算 ... [详细]
  • 回顾两年前春节期间的一个个人项目,该项目原本计划参加竞赛,但最终作为练习项目完成。独自完成了从编码到UI设计的全部工作,尽管代码量不大,但仍有一定的参考价值。本文将详细介绍该项目的背景、功能及技术实现。 ... [详细]
  • 如何在PHP中安装Xdebug扩展
    本文介绍了如何从PECL下载并编译安装Xdebug扩展,以及如何配置PHP和PHPStorm以启用调试功能。 ... [详细]
author-avatar
幸福不要躲008_784
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有