热门标签 | 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


推荐阅读
  • 动态规划算法的基本步骤及最长递增子序列问题详解
    本文详细介绍了动态规划算法的基本步骤,包括划分阶段、选择状态、决策和状态转移方程,并以最长递增子序列问题为例进行了详细解析。动态规划算法的有效性依赖于问题本身所具有的最优子结构性质和子问题重叠性质。通过将子问题的解保存在一个表中,在以后尽可能多地利用这些子问题的解,从而提高算法的效率。 ... [详细]
  • 本文介绍了C#中数据集DataSet对象的使用及相关方法详解,包括DataSet对象的概述、与数据关系对象的互联、Rows集合和Columns集合的组成,以及DataSet对象常用的方法之一——Merge方法的使用。通过本文的阅读,读者可以了解到DataSet对象在C#中的重要性和使用方法。 ... [详细]
  • 本文介绍了使用Java实现大数乘法的分治算法,包括输入数据的处理、普通大数乘法的结果和Karatsuba大数乘法的结果。通过改变long类型可以适应不同范围的大数乘法计算。 ... [详细]
  • HDU 2372 El Dorado(DP)的最长上升子序列长度求解方法
    本文介绍了解决HDU 2372 El Dorado问题的一种动态规划方法,通过循环k的方式求解最长上升子序列的长度。具体实现过程包括初始化dp数组、读取数列、计算最长上升子序列长度等步骤。 ... [详细]
  • 本文介绍了OC学习笔记中的@property和@synthesize,包括属性的定义和合成的使用方法。通过示例代码详细讲解了@property和@synthesize的作用和用法。 ... [详细]
  • 1,关于死锁的理解死锁,我们可以简单的理解为是两个线程同时使用同一资源,两个线程又得不到相应的资源而造成永无相互等待的情况。 2,模拟死锁背景介绍:我们创建一个朋友 ... [详细]
  • 本文介绍了指针的概念以及在函数调用时使用指针作为参数的情况。指针存放的是变量的地址,通过指针可以修改指针所指的变量的值。然而,如果想要修改指针的指向,就需要使用指针的引用。文章还通过一个简单的示例代码解释了指针的引用的使用方法,并思考了在修改指针的指向后,取指针的输出结果。 ... [详细]
  • 猜字母游戏
    猜字母游戏猜字母游戏——设计数据结构猜字母游戏——设计程序结构猜字母游戏——实现字母生成方法猜字母游戏——实现字母检测方法猜字母游戏——实现主方法1猜字母游戏——设计数据结构1.1 ... [详细]
  • VScode格式化文档换行或不换行的设置方法
    本文介绍了在VScode中设置格式化文档换行或不换行的方法,包括使用插件和修改settings.json文件的内容。详细步骤为:找到settings.json文件,将其中的代码替换为指定的代码。 ... [详细]
  • 本文讨论了如何优化解决hdu 1003 java题目的动态规划方法,通过分析加法规则和最大和的性质,提出了一种优化的思路。具体方法是,当从1加到n为负时,即sum(1,n)sum(n,s),可以继续加法计算。同时,还考虑了两种特殊情况:都是负数的情况和有0的情况。最后,通过使用Scanner类来获取输入数据。 ... [详细]
  • 如何用UE4制作2D游戏文档——计算篇
    篇首语:本文由编程笔记#小编为大家整理,主要介绍了如何用UE4制作2D游戏文档——计算篇相关的知识,希望对你有一定的参考价值。 ... [详细]
  • 本文详细介绍了Linux中进程控制块PCBtask_struct结构体的结构和作用,包括进程状态、进程号、待处理信号、进程地址空间、调度标志、锁深度、基本时间片、调度策略以及内存管理信息等方面的内容。阅读本文可以更加深入地了解Linux进程管理的原理和机制。 ... [详细]
  • 本文介绍了P1651题目的描述和要求,以及计算能搭建的塔的最大高度的方法。通过动态规划和状压技术,将问题转化为求解差值的问题,并定义了相应的状态。最终得出了计算最大高度的解法。 ... [详细]
  • 本文探讨了C语言中指针的应用与价值,指针在C语言中具有灵活性和可变性,通过指针可以操作系统内存和控制外部I/O端口。文章介绍了指针变量和指针的指向变量的含义和用法,以及判断变量数据类型和指向变量或成员变量的类型的方法。还讨论了指针访问数组元素和下标法数组元素的等价关系,以及指针作为函数参数可以改变主调函数变量的值的特点。此外,文章还提到了指针在动态存储分配、链表创建和相关操作中的应用,以及类成员指针与外部变量的区分方法。通过本文的阐述,读者可以更好地理解和应用C语言中的指针。 ... [详细]
  • Linux环境变量函数getenv、putenv、setenv和unsetenv详解
    本文详细解释了Linux中的环境变量函数getenv、putenv、setenv和unsetenv的用法和功能。通过使用这些函数,可以获取、设置和删除环境变量的值。同时给出了相应的函数原型、参数说明和返回值。通过示例代码演示了如何使用getenv函数获取环境变量的值,并打印出来。 ... [详细]
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社区 版权所有