`

《An Introduction to Boost》Part I 通用库 scoped_ptr指针

 
阅读更多

 

scoped_ptr

头文件: "boost/scoped_ptr.hpp"

boost::scoped_ptr 用于确保能够正确地删除动态分配的对象。scoped_ptr 有着与std::auto_ptr类似的特性,而最大的区别在于它不能转让所有权而auto_ptr可以。事实上,scoped_ptr永远不能被复制或被赋值!scoped_ptr 拥有它所指向的资源的所有权,并永远不会放弃这个所有权。scoped_ptr的这种特性提升了我们的代码的表现,我们可以根据需要选择最合适的智能指针(scoped_ptrauto_ptr)。

要决定使用std::auto_ptr还是boost::scoped_ptr, 就要考虑转移所有权是不是你想要的智能指针的一个特性。如果不是,就用scoped_ptr. 它是一种轻量级的智能指针;使用它不会使你的程序变大或变慢。它只会让你的代码更安全,更好维护。

下面是scoped_ptr的摘要,以及对它的成员的简要描述:

namespace boost {

  template<typename T> class scoped_ptr : noncopyable {
  public:
    explicit scoped_ptr(T* p = 0); 
    ~scoped_ptr(); 

    void reset(T* p = 0); 

    T& operator*() const; 
    T* operator->() const; 
    T* get() const; 
   
    void swap(scoped_ptr& b); 
  };

  template<typename T> 
    void swap(scoped_ptr<T> & a, scoped_ptr<T> & b); 
}

成员函数

explicit scoped_ptr(T* p=0)

构造函数,存储p的一份拷贝。注意,p 必须是用operator new分配的,或者是null. 在构造的时候,不要求T必须是一个完整的类型。当指针p是调用某个分配函数的结果而不是直接调用new得到的时候很有用:因为这个类型不必是完整的,只需要类型T的一个前向声明就可以了。这个构造函数不会抛出异常。

~scoped_ptr()

删除被指物。类型T在被销毁时必须是一个完整的类型。如果scoped_ptr在它被析构时并没有保存资源,它就什么都不做。这个析构函数不会抛出异常。

void reset(T* p=0);

重置一个 scoped_ptr 就是删除它已保存的指针,如果它有的话,并重新保存 p. 通常,资源的生存期管理应该完全由scoped_ptr自己处理,但是在极少数时候,资源需要在scoped_ptr的析构之前释放,或者scoped_ptr要处理它原有资源之外的另外一个资源。这时,就可以用reset,但一定要尽量少用它。(过多地使用它通常表示有设计方面的问题) 这个函数不会抛出异常。

T& operator*() const;

返回一个到被保存指针指向的对象的引用。由于不允许空的引用,所以解引用一个拥有空指针的scoped_ptr将导致未定义行为。如果不能肯定所含指针是否有效,就用函数get替代解引用。这个函数不会抛出异常。

T* operator->() const;

返回保存的指针。如果保存的指针为空,则调用这个函数会导致未定义行为。如果不能肯定指针是否空的,最好使用函数get。这个函数不会抛出异常。

T* get() const;

返回保存的指针。应该小心地使用get,因为它可以直接操作裸指针。但是,get使得你可以测试保存的指针是否为空。这个函数不会抛出异常。get通常用于调用那些需要裸指针的函数。

operator unspecified_bool_type() const

返回scoped_ptr是否为非空。返回值的类型是未指明的,但这个类型可被用于Boolean的上下文中。在if语句中最好使用这个类型转换函数,而不要用get去测试scoped_ptr的有效性

void swap(scoped_ptr& b)

交换两个scoped_ptr的内容。这个函数不会抛出异常。

普通函数

template<typename T> void swap(scoped_ptr<T>& a,scoped_ptr<T>& b)

这个函数提供了交换两个scoped pointer的内容的更好的方法。之所以说它更好,是因为 swap(scoped1,scoped2) 可以更广泛地用于很多指针类型,包括裸指针和第三方的智能指针。[2]scoped1.swap(scoped2) 则只能用于它的定义所在的智能指针,而不能用于裸指针。

[2] 你可为那些不够智能,没有提供它们自己的交换函数的智能指针创建你的普通swap函数。

用法

scoped_ptr的用法与普通的指针没什么区别;最大的差别在于你不必再记得在指针上调用delete,还有复制是不允许的。典型的指针操作(operator*operator->)都被重载了,并提供了和裸指针一样的语法。用scoped_ptr和用裸指针一样快,也没有大小上的增加,因此它们可以广泛使用。使用boost::scoped_ptr时,包含头文件"boost/scoped_ptr.hpp". 在声明一个scoped_ptr时,用被指物的类型来指定类模板的参数。例如,以下是一个包含std::string指针的scoped_ptr

boost::scoped_ptr<std::string> p(new std::string("Hello"));

scoped_ptr被销毁时,它对它所拥有的指针调用delete

不需要手工删除

让我们看一个程序,它使用scoped_ptr来管理std::string指针。注意这里没有对delete的调用,因为scoped_ptr是一个自动变量,它会在离开作用域时被销毁。

#include "boost/scoped_ptr.hpp"
#include <string>
#include <iostream>

int main() {
  {
  boost::scoped_ptr<std::string> 
  p(new std::string("Use scoped_ptr often."));

  // 打印字符串的值
  if (p)
    std::cout << *p << '\n';
    
  // 获取字符串的大小
  size_t i=p->size();

  // 给字符串赋新值
  *p="Acts just like a pointer";
  
  } // 这里p被销毁,并删除std::string 
}

这段代码中有几个地方值得注明一下。首先,scoped_ptr可以测试其有效性,就象一个普通指针那样,因为它提供了隐式转换到一个可用于布尔表达式的类型的方法。其次,可以象使用裸指针那样调用被被指物的成员函数,因为重载了operator->. 第三,也可以和裸指针一样解引用scoped_ptr,这归功于operator*的重载。这些特性正是scoped_ptr和其它智能指针的用处所在,因为它们和裸指针的不同之处在于对生存期管理的语义上,而不在于语法上。

auto_ptr几乎一样

scoped_ptrauto_ptr间的区别主要在于对拥有权的处理。auto_ptr在复制时会从源auto_ptr自动交出拥有权,而scoped_ptr则不允许被复制。看看下面这段程序,它把scoped_ptrauto_ptr放在一起,你可以清楚地看到它们有什么不同。

void scoped_vs_auto() {

  using boost::scoped_ptr;
  using std::auto_ptr;

  scoped_ptr<std::string> p_scoped(new std::string("Hello"));
  auto_ptr<std::string> p_auto(new std::string("Hello"));

  p_scoped->size();
  p_auto->size();

  scoped_ptr<std::string> p_another_scoped=p_scoped;
  auto_ptr<std::string> p_another_auto=p_auto;

  p_another_auto->size();
  (*p_auto).size();
}

这个例子不能通过编译,因为scoped_ptr不能被复制构造或被赋值。auto_ptr既可以复制构造也可以赋值,但这们同时也意味着它把所有权从p_auto 转移给了 p_another_auto, 在赋值后p_auto将只剩下一个空指针。这可能会导致令人不快的惊讶,例如你试图把auto_ptr放入容器内。[3] 如果我们删掉对p_another_scoped的赋值,程序就可以编译了,但它的运行结果是不可预测的,因为它解引用了p_auto里的空指针(*p_auto).

[3] 永远不要把auto_ptr放入标准库的容器里。如果你试一下,通常你会得到一个编译错误;如果你没有得到错误,你就麻烦了。

由于scoped_ptr::get会返回一个裸指针,所以就有可能对scoped_ptr做一些有害的事情,其中有两件是你尤其要避免的。第一,不要删除这个裸指针。因为它会在scoped_ptr被销毁时再一次被删除。第二,不要把这个裸指针保存到另一个scoped_ptr (或其它任何的智能指针)里。因为这样也会两次删除这个指针,每个scoped_ptr一次。简单地说,尽量少用get, 除非你要使用那些要求你传送裸指针的遗留代码!

scoped_ptr 和Pimpl用法

scoped_ptr可以很好地用于许多以前使用裸指针或auto_ptr的地方,如在实现pimpl用法时。[4]pimpl 用法背后的思想是把客户与所有关于类的私有部分的知识隔离开。由于客户是依赖于类的头文件的,头文件中的任何变化都会影响客户,即使仅是对私有节或保护节的修改。pimpl用法隐藏了这些细节,方法是将私有数据和函数放入一个单独的类中,并保存在一个实现文件中,然后在头文件中对这个类进行前向声明并保存一个指向该实现类的指针。类的构造函数分配这个pimpl类,而析构函数则释放它。这样可以消除头文件与实现细节的相关性。我们来构造一个实现pimpl 用法的类,然后用智能指针让它更安全些。

[4] 这也被称为Cheshire Cat 用法. 关于pimpl用法更多的说明请见 www.gotw.ca/gotw/024.htm 和 Exceptional C++ 。

// pimpl_sample.hpp

#if !defined (PIMPL_SAMPLE)
#define PIMPL_SAMPLE

class pimpl_sample {
  struct impl;  // 译者注:原文中这句在class之外,应有误
  impl* pimpl_;
public:
  pimpl_sample();
  ~pimpl_sample();
  void do_something();
};

#endif

这是类pimpl_sample的接口。struct impl 是一个前向声明,它把所有私有成员和函数放在另一个实现文件中。这样做的效果是使客户与pimpl_sample类的内部细节完全隔离开来。

// pimpl_sample.cpp 

#include "pimpl_sample.hpp"
#include <string>
#include <iostream>

struct pimpl_sample::impl {
  void do_something_() {
    std::cout << s_ << "\n";
  }

  std::string s_;
};

pimpl_sample::pimpl_sample()
  : pimpl_(new impl) {
  pimpl_->s_ = "This is the pimpl idiom";
}

pimpl_sample::~pimpl_sample() {
  delete pimpl_;
}

void pimpl_sample::do_something() {
  pimpl_->do_something_();
}

看起来很完美,但并不是的。这个实现不是异常安全的!原因是pimpl_sample的构造函数有可能在pimpl被构造后抛出一个异常。在构造函数中抛出异常意味着已构造的对象并不存在,因此在栈展开时将不会调用它的析构函数。这样就意味着分配给impl_指针的内存将泄漏。然而,有一样简单的解决方法:用scoped_ptr来解救!

class pimpl_sample {
  struct impl;
  boost::scoped_ptr<impl> pimpl_;
  ...
};

scoped_ptr来处理隐藏类impl的生存期管理,并从析构函数中去掉对impl的删除(它不再需要,这要感谢scoped_ptr),这样就做完了。但是,你必须记住要手工定义析构函数;原因是在编译器生成隐式析构函数时,类impl还是不完整的,所以它的析构函数不能被调用。如果你用auto_ptr来保存impl, 你可以编译,但也还是有这个问题,但如果用scoped_ptr, 你将收到一个错误提示。

要注意的是,如果你使用scoped_ptr作为一个类的成员,你就必须手工定义这个类的复制构造函数和赋值操作符。原因是scoped_ptr是不能复制的,因此聚集了它的类也变得不能复制了。

最后一点值得注意的是,如果pimpl实例可以安全地被多个封装类(在这里是pimpl_sample)的实例所共享,那么用boost::shared_ptr来管理pimpl的生存期才是正确的选择。用shared_ptr比用scoped_ptr的优势在于,不需要手工去定义复制构造函数和赋值操作符,而且可以定义空的析构函数,shared_ptr被设计为可以正确地用于未完成的类。

scoped_ptr 不同于 const auto_ptr

留心的读者可能已经注意到auto_ptr可以几乎象scoped_ptr一样地工作,只要把auto_ptr声明为const

const auto_ptr<A> no_transfer_of_ownership(new A);

它们很接近,但不是一样。最大的区别在于scoped_ptr可以被reset, 在需要时可以删除并替换被指物。而对于const auto_ptr这是不可能的。另一个小一点的区别是,它们的名字不同:尽管const auto_ptr意思上和scoped_ptr一样,但它更冗长,也更不明显。当你的词典里有了scoped_ptr,你就应该使用它,因为它可以更清楚地表明你的意图。如果你想说一个资源是要被限制在作用域里的,并且不应该有办法可以放弃它的所有权,你就应该用 boost::scoped_ptr.

总结

使用裸指针来写异常安全和无错误的代码是很复杂的。使用智能指针来自动地把动态分配对象的生存期限制在一个明确的范围之内,是解决这种问题的一个有效的方法,并且提高了代码的可读性、可维护性和质量。scoped_ptr 明确地表示被指物不能被共享和转移。正如你所看到的,std::auto_ptr可以从另一个auto_ptr那里窃取被指物,那怕是无意的,这被认为是auto_ptr的最大的缺点。正是这个缺点使得scoped_ptr成为auto_ptr最好的补充。当一个动态分配的对象被传送给scoped_ptr, 它就成为了这个对象的唯一的拥有者。因为scoped_ptr几乎总是以自动变量或数据成员来分配的,因此它可以在离开作用域时正确地销毁,从而在执行流由于返回语句或异常抛出而离开作用域时,总能释放它所管理的内存。

在以下情况时使用 scoped_ptr

  • 在可能有异常抛出的作用域里使用指针

  • 函数里有几条控制路径

  • 动态分配对象的生存期应被限制于特定的作用域内

  • 异常安全非常重要时(始终如此!)

分享到:
评论

相关推荐

    浅析Boost智能指针:scoped_ptr shared_ptr weak_ptr

    scoped_ptrboost::scoped_ptr和std::auto_ptr非常类似,是一个简单的智能指针,它能够保证在离开作用域后对象被自动释放。下列代码演示了该指针的基本应用: 代码如下:#include &lt;string&gt;#include &lt;iostream&gt;#...

    一键删除全部Selenium缓存(scoped-dir缓存文件)

    ●拿到脚本后,可以利用pyinstaller库进行封装生成一个程序。方便点击一键删除缓存。 ●代码:pyinstaller -F 文件名.py,生成程序后,在py文件目录下生成的dist文件夹里生成程序文件。直接将其拷贝到自己能操作的...

    C++智能指针用法详解

    一、简介  由于 C++ 语言没有自动...包括:std::auto_ptr、boost::scoped_ptr、boost::shared_ptr、boost::scoped_array、boost::shared_array、boost::weak_ptr、boost:: intrusive_ptr。你可能会想,如此多的智能指

    C++智能指针详解.pdf

    包 括:std::auto_ptrboost::scoped_ptr、boost::shared_ptr、boost::scoped_array、、boost::weak_ptr、boost::intrusive_ptr。你可能会想,如 此多的智能指针就为了解决new、delete匹配问题,真的有必要吗?看完这...

    C++11 下使用 Boost.Serialization 库实现智能指针的序列化

    C++11 下使用 Boost.Serialization 库实现智能指针的序列化

    C++智能指针实例详解

    本文通过实例详细阐述了C++关于智能指针的概念及用法,有助于读者加深对智能指针的理解。...包括:std::auto_ptr、boost::scoped_ptr、boost::shared_ptr、boost::scoped_array、boost::shared_array、boost::

    c++ boost 开发参考手册

    最近开发中用到大量BOOST库的东西,现在把我开发中总结的boost使用方法分享一下,里面包括智能指针、boost定时器、boostthread和io_service等的用法和说明,还有一本Beyond the C++ Standard Library: An ...

    程序员需要经常刷题吗-scoped_attr_accessor:将私有和受保护的attr_accessor方法添加到您的类-或所有ruby

    'scoped_attr_accessor' 然后执行: $ bundle 或者自己安装: $ gem install scoped_attr_accessor 用法 您可以通过直接在您的类中扩展ScopedAttrAccessor来将范围访问器添加到单个类(及其子类): require 'scoped...

    lab03_shared_ptr

    Например,класс scoped_refptr (аналог std::shared_ptr )используетсявChrome 。 Чтобылучшеусвоитьипонятьпринципработыэтого...

    scoped_from:提供Ruby On Rails的范围和控制器参数之间的简单映射

    gem 'scoped_from' 然后,只需运行bundle install 。 例子 首先,有一些范围的模型: class Post &lt; ActiveRecord&gt; 0' ) scope :created_between , lambda { | after , before | where ( 'created_at &gt;= ? AND ...

    【转发】基于Boost的数据处理器及线程安全类和信号量

    1. 线程安全的双端队列,可用于多线程中传递数据,数据类型包括二进制流、简单类型、对象等。 2. 封装了boost的condition_variable,使其使用方法很接近Windows...4. 封装了boost的mutex的scoped_lock,能跨平台使用。

    scoped-threadpool-rs

    scoped_threadpool = " 0.1.* " 要始终获取最新版本,请将此git存储库添加到您的货运清单中: [ dependencies . scoped_threadpool ] git = " https://github.com/Kimundi/scoped-threadpool-rs " 例子 extern ...

    基于Boost的数据处理器及线程安全类和信号量

    4. 封装了boost的mutex的scoped_lock,能跨平台使用。相对于CWnLock,其优势在于发生异常时能自动解锁,避免线程死锁。 5. // 一个可用于线程间传递数据的类。此类的优势在于: // 1. 跨平台 // 2. 将线程通信间...

    Flutter-Coin-App-Scoped_Model-API-Json:API Json的范围模型

    crypto_app 一个新的Flutter项目。 入门 要获得Flutter入门方面的帮助,请查看我们的在线。

    利用python清除移动硬盘中的临时文件

    1、目标场景 用过Mac OS的朋友应该都遇到过,日常的文件操作会在同级目录下产生一些特定的临时文件。 平常将文件夹打包成压缩包或拷贝文件夹到移动硬盘内,临时文件会包含在里面,如果是程序源代码,在Windows系统下...

    THE BOOST C++ LIBRARIES

    THE BOOST C++ LIBRARIES是一份自己编译的chm格式文档,描述了如何使用boost类库,目录如下: Front page Chapter 1: Introduction 1.1 C++ and Boost 1.2 Development Process 1.3 Installation 1.4 Overview ...

    marshmallow-sqlalchemy:SQLAlchemy 与棉花糖的集成

    棉花糖-sqlalchemy 主页: : 与(反)序列化库的集成。...session = scoped_session ( sessionmaker ( bind = engine )) Base = declarative_base () class Author ( Base ): __tablename__ = "aut

    Google C++ Style Guide(Google C++编程规范)高清PDF

    Sometimes it makes sense to have pointer (or better, scoped_ptr) members instead of object members. However, this complicates code readability and imposes a performance penalty, so avoid doing this ...

    flutter-todo-scoped-model:使用Flutter和ScopedModel构建的基本todo应用

    flutter-todo-scoped-model:使用Flutter和ScopedModel构建的基本todo应用

Global site tag (gtag.js) - Google Analytics