美文网首页
Boost 智能指针

Boost 智能指针

作者: 戴廿叁 | 来源:发表于2019-12-20 11:18 被阅读0次

    1. 概述

    C++没有提供类似JAVA的垃圾回收机制,所以对象不会在不使用时自动销毁。尽管STL提供了 std::auto_ptr 智能指针,它会在析构的时候调用 delete 操作符来自动释放所包含的对象,从而避免内存泄漏、越界访问这类问题。但是由于其不支持拷贝和赋值操作,所以很少被使用。

    Boost库为我们提供了多种智能指针:

    类型 特点
    作用域指针 boost::scoped_ptr 不支持拷贝和赋值操作,承载new,只能在 scoped_ptr 声明的作用域内使用
    作用域数组 boost::scoped_array 和scope_ptr类似,不支持拷贝和赋值操作,承载new[]
    共享指针 boost::shared_ptr 有一个引用指针记数器,表示类型T的对象是否已经不再使用可拷贝,承载new。boost 库中重要组成,使用比较多
    共享数组 boost::shared_array 和 shared_ptr 类似,用来处理数组
    弱指针 boost::weak_ptr 同shared_ptr一起使用,帮助shared_ptr 避免循环引用
    介入式指针 boost::intrusive_ptr 比 shared_ptr 更好的智能指针,但是需要自己实现引用记数机制

    2. 指针说明

    2.1 作用域指针

    一个作用域指针独占一个动态分配的对象。 对应的类名为 ==boost::scoped_ptr==,它的定义在 boost/scoped_ptr.hpp 中。 不像 std::auto_ptr,一个作用域指针不能传递它所包含的对象的所有权到另一个作用域指针。 一旦用一个地址来初始化,这个动态分配的对象将在析构阶段释放。

    因为一个作用域指针只是简单保存和独占一个内存地址,所以 boost::scoped_ptr 的实现就要比 std::auto_ptr 简单。 在不需要所有权传递的时候应该优先使用 boost::scoped_ptr 。 在这些情况下,比起 std::auto_ptr 它是一个更好的选择,因为可以避免不经意间的所有权传递。

    示例:

    #include <boost/scoped_ptr.hpp> 
    
    int main() 
    { 
      boost::scoped_ptr<int> i(new int); 
      *i = 1; 
      *i.get() = 2; 
      i.reset(new int); 
    } 
    

    一经初始化,智能指针 boost::scoped_ptr 所包含的对象,可以通过类似于普通指针的接口来访问。 这是因为重载了相关的操作符 ==operator*()====,operator->()== 和 ==operator bool()== 。 此外,还有 ==get()== 和 ==reset()== 方法。 前者返回所含对象的地址,后者用一个新的对象来重新初始化智能指针。 在这种情况下,新创建的对象赋值之前会先自动释放所包含的对象。

    boost::scoped_ptr 的析构函数中使用 delete 操作符来释放所包含的对象。这对 boost::scoped_ptr 所包含的类型加上了一条重要的限制。 boost::scoped_ptr 不能用动态分配的数组来做初始化,因为这需要调用 delete[] 来释放。 在这种情况下,可以使用下面将要介绍的 boost:scoped_array 类。

    总结:

    • 只限于作用域内使用
    • 指针管理权不可转移,不支持拷贝构造函数与赋值操作

    2.2 作用域数组

    作用域数组的使用方式与作用域指针相似。 关键不同在于,作用域数组的析构函数使用 delete[] 操作符来释放所包含的对象。 因为该操作符只能用于数组对象,所以作用域数组必须通过动态分配的数组来初始化。

    对应的作用域数组类名为 ==boost::scoped_array==,它的定义在 boost/scoped_array.hpp 里。

    示例:

    #include <boost/scoped_array.hpp> 
    
    int main() 
    { 
      boost::scoped_array<int> i(new int[2]); 
      *i.get() = 1; 
      i[1] = 2; 
      i.reset(new int[3]); 
    } 
    

    boost:scoped_array 类重载了操作符 ==operator[]()== 和 ==operator bool()==。 可以通过 operator 操作符访问数组中特定的元素,于是 boost::scoped_array 类型对象的行为就酷似它所含的数组。

    正如 boost::scoped_ptr 那样, boost:scoped_array 也提供了 ==get()== 和 ==reset()== 方法,用来返回和重新初始化所含对象的地址。

    总结:

    • 构造函数指针必须是 new[] 的结果,而不能是 new 表达式的结果
    • 没有 *, -> 操作符重载,因为 scoped_array 持有的不是一个普通指针
    • 析构函数使用 delete[] 释放资源,而不是 delete
    • 提供 operator[] 操作符重载,可以像普通数组一样用下标访问
    • 没有 begin(), end() 等类似容器迭代器操作函数

    2.3 共享指针(重中之重)

    这是使用率最高的智能指针,但是 C++ 标准的第一版中缺少这种指针。 它已经作为技术报告1(TR 1)的一部分被添加到标准里了。 如果开发环境支持的话,可以使用 memory 中定义的 std::shared_ptr。 在 Boost C++ 库里,这个智能指针命名为 ==boost::shared_ptr==,定义在 boost/shared_ptr.hpp 里。

    智能指针 boost::shared_ptr 基本上类似于 boost::scoped_ptr。 关键不同之处在于 boost::shared_ptr 不一定要独占一个对象。 它可以和其他 boost::shared_ptr 类型的智能指针共享所有权。 boost::shared_ptr 内置引用计数,引用指针计数器记录有多少个引用指针指向同一个对象。智能指针赋值拷贝的同时,引用计数也加1了。当引用对象的最后一个智能指针销毁后(引用计数都为0后),对象才会被释放

    因为所有权可以在 boost::shared_ptr 之间共享,任何一个共享指针都可以被复制,这跟 boost::scoped_ptr 是不同的。 这样就可以在标准容器里存储智能指针了—— 你不能在标准容器中存储 std::auto_ptr,因为它们在拷贝的时候传递了所有权。

    示例1:

    #include <boost/shared_ptr.hpp> 
    #include <vector> 
    
    int main() 
    { 
      std::vector<boost::shared_ptr<int> > v; 
      v.push_back(boost::shared_ptr<int>(new int(1))); 
      v.push_back(boost::shared_ptr<int>(new int(2))); 
    }  
    

    多亏了有 boost::shared_ptr,我们才能像上例中展示的那样,在标准容器中安全的使用动态分配的对象。 因为 boost::shared_ptr 能够共享它所含对象的所有权,所以保存在容器中的拷贝(包括容器在需要时额外创建的拷贝)都是和原件相同的。如前所述,std::auto_ptr做不到这一点,所以绝对不应该在容器中保存它们。

    类似于 boost::scoped_ptr, boost::shared_ptr 类重载了以下这些操作符:operator*(),operator->() 和 operator bool()。另外还有 get() 和 reset() 函数来获取和重新初始化所包含的对象的地址。

    示例2:

    #include <boost/shared_ptr.hpp> 
    
    int main() 
    { 
      boost::shared_ptr<int> i1(new int(1)); 
      boost::shared_ptr<int> i2(i1); 
      i1.reset(new int(2)); 
    } 
    

    本例中定义了2个共享指针 i1 和 i2,它们都引用到同一个 int 类型的对象。i1 通过 new 操作符返回的地址显示的初始化,i2 通过 i1 拷贝构造而来。 i1 接着调用 reset(),它所包含的整数的地址被重新初始化。不过它之前所包含的对象并没有被释放,因为 i2 仍然引用着它。 智能指针 boost::shared_ptr 记录了有多少个共享指针在引用同一个对象,只有在最后一个共享指针销毁时才会释放这个对象

    默认情况下,boost::shared_ptr 使用 delete 操作符来销毁所含的对象。 然而,具体通过什么方法来销毁,是可以指定的,就像下面的例子里所展示的:

    示例3:

    #include <boost/shared_ptr.hpp> 
    #include <windows.h> 
    
    int main() 
    { 
      boost::shared_ptr<void> h(OpenProcess(PROCESS_SET_INFORMATION, FALSE, GetCurrentProcessId()), CloseHandle); 
      SetPriorityClass(h.get(), HIGH_PRIORITY_CLASS); 
    } 
    

    boost::shared_ptr 的构造函数的第二个参数是一个普通函数或者函数对象,该参数用来销毁所含的对象。 在本例中,这个参数是 Windows API 函数 CloseHandle()。 当变量 h 超出它的作用域时,调用的是这个函数而不是 delete 操作符来销毁所含的对象。 为了避免编译错误,该函数只能带一个 HANDLE 类型的参数, CloseHandle() 正好符合要求。

    总结:

    • 支持拷贝构造函数,赋值操作
    • 重载 * 和 -> 操作符模仿原始指针
    • 内置引用计数
    • 同scope_ptr一样,禁止get()得到指针地址后,执行delete
    • 禁止循环引用,否则会出内存泄漏
    • 不能作为函数的临时参数
    • shared_ptr是线程安全的
    • shared_ptr支持强制类型转换,如果定义了一个U能够强制转换到T(因为T是U的基类),那么shared_ptr也能够强制转换到shared_ptr

    2.4 共享数组

    共享数组的行为类似于共享指针。 关键不同在于共享数组在析构时,默认使用 delete[] 操作符来释放所含的对象。 因为这个操作符只能用于数组对象,共享数组必须通过动态分配的数组的地址来初始化。

    共享数组对应的类型是 ==boost::shared_array==,它的定义在 boost/shared_array.hpp 里。

    示例:

    #include <boost/shared_array.hpp> 
    #include <iostream> 
    
    int main() 
    { 
      boost::shared_array<int> i1(new int[2]); 
      boost::shared_array<int> i2(i1); 
      i1[0] = 1; 
      std::cout << i2[0] << std::endl; 
    } 
    

    就像共享指针那样,所含对象的所有权可以跟其他共享数组来共享。 这个例子中定义了2个变量 i1 和 i2,它们引用到同一个动态分配的数组。i1 通过 operator 操作符保存了一个整数1 —— 这个整数可以被 i2 引用,比如打印到标准输出。

    boost::shared_array 也同样提供了 get() 和 reset() 方法。 另外还重载了 operator bool()。

    2.5 弱指针

    boost::weak_ptr 必定总是通过 boost::shared_ptr 来初始化的。一旦初始化之后,它基本上只提供一个有用的方法: ==lock()==。此方法返回的boost::shared_ptr 与用来初始化弱指针的共享指针共享所有权。 如果这个共享指针不含有任何对象,返回的共享指针也将是空的。

    当函数需要一个由共享指针所管理的对象,而这个对象的生存期又不依赖于这个函数时,就可以使用弱指针。 只要程序中还有一个共享指针掌管着这个对象,函数就可以使用该对象。 如果共享指针复位了,就算函数里能得到一个共享指针,对象也不存在了。

    boost::weak_ptr 没有共享资源,它的构造不会引起指针引用计数的增加,同时,在析构的时候也不回引起引用计数的减少。

    示例:

    #include <windows.h> 
    #include <boost/shared_ptr.hpp> 
    #include <boost/weak_ptr.hpp> 
    #include <iostream> 
    
    DWORD WINAPI reset(LPVOID p) 
    { 
      boost::shared_ptr<int> *sh = static_cast<boost::shared_ptr<int>*>(p); 
      sh->reset(); 
      return 0; 
    } 
    
    DWORD WINAPI print(LPVOID p) 
    { 
      boost::weak_ptr<int> *w = static_cast<boost::weak_ptr<int>*>(p); 
      boost::shared_ptr<int> sh = w->lock(); 
      if (sh) 
        std::cout << *sh << std::endl; 
      return 0; 
    } 
    
    int main() 
    { 
      boost::shared_ptr<int> sh(new int(99)); 
      boost::weak_ptr<int> w(sh); 
      HANDLE threads[2]; 
      threads[0] = CreateThread(0, 0, reset, &sh, 0, 0); 
      threads[1] = CreateThread(0, 0, print, &w, 0, 0); 
      WaitForMultipleObjects(2, threads, TRUE, INFINITE); 
    } 
    

    上例的 main() 函数中,通过 Windows API 创建了2个线程。第一个线程函数 reset() 的参数是一个共享指针的地址。 第二个线程函数 print() 的参数是一个弱指针的地址。 这个弱指针是之前通过共享指针初始化的。

    一旦程序启动之后,reset() 和 print() 就都开始执行了。 不过执行顺序是不确定的。 这就导致了一个潜在的问题:reset() 线程在销毁对象的时候print() 线程可能正在访问它。通过调用弱指针的 lock() 函数可以解决这个问题:如果对象存在,那么 lock() 函数返回的共享指针指向这个合法的对象。否则,返回的共享指针被设置为0,这等价于标准的null指针。

    弱指针本身对于对象的生存期没有任何影响。 lock() 返回一个共享指针,print() 函数就可以安全的访问对象了。 这就保证了——即使另一个线程要释放对象——由于我们有返回的共享指针,对象依然存在。

    2.6 介入式指针

    大体上,介入式指针的工作方式和共享指针完全一样。区别是它实际并不提供引用计数功能,而是要求被存储的对象自己实现引用计数功能,并提供intrusive_ptr_add_ref和intrusive_ptr_release函数接口供boost::intrusive_ptr调用。

    介入式指针 ==boost::intrusive_ptr== 定义在 boost/intrusive_ptr.hpp 里。

    没有用到,暂时不做详解。

    相关文章

      网友评论

          本文标题:Boost 智能指针

          本文链接:https://www.haomeiwen.com/subject/kpibnctx.html