中国到瑞典多远:智能指针学习

来源:百度文库 编辑:偶看新闻 时间:2024/04/30 02:25:55

最近接触到智能指针很多,于是研究了一下智能指针的原理,写下自己的心得体会,有不对的还请指正。

智能指针产生的目的:因为在C++中,存在非常复杂的指针错误问题,例如,某个对象生成后,指向该对象的指针可能有多个,当我们用delete语句删除其中的一个指针后,对象就被销毁,那么其余指向该对象的指针就会悬空,这样很容易出错内存误,为避免出现这样的问题,出现了智能指针,智能指针有2种构造方法,一种是插入式的还有一种是非插入式的,非插入式指针一般是直接采用裸指针作为参数进行创建,不需要修改现有的对象代码,而插入式是采用一个公用的有数量统计功能的基类来派生需要智能指针的类,相对来说,插入式构造方法将需要更多的额外空间,而且需要修改原类。非插入式智能指针(shared_ptr)可以从裸指针,另一个shared_ptr、一个std::auto_ptr、或者一个boost::weak_ptr构造,还可以传递第二个参数给shared_ptr的构造函数,它被称为删除器(deleter)。删除器稍后会被调用,来处理共享资源的释放。这对于管理那些不是用new分配也不是用delete释放的资源时非常有用。shared_ptr被创建后,它就可象普通指针一样使用了,除了一点,它不能被显式地删除,列举一个shared_ptr的例子,有时候把对象直接存入容器中有时会有些麻烦,以值的方式保存对象意味着使用者将获得容器中的元素的拷贝,对于那些复制是一种昂贵的操作的类型来说可能会有性能的问题。此外,有些容器,特别是 std::vector, 当你加入元素时可能会复制所有元素,这更加重了性能的问题。最后,传值的语义意味着没有多态的行为。如果你需要在容器中存放多态的对象而且你不想切割它们,你必须用指针。如果你用裸指针,维护元素的完整性会非常复杂。从容器中删除元素时,你必须知道容器的使用者是否还在引用那些要删除的元素,不用担心多个使用者使用同一个元素。这些问题都可以用shared_ptr来解决。插入式版本。有时我们必须使用插入式的引用计数智能指针。典型的情况是对于那些已经写好了内部引用计数器的代码,而我们又没有时间去重写它(或者已经不能获得那些代码了)。另一种情况是要求智能指针的大小必须与裸指针大小严格相等,或者shared_ptr的引用计数器分配严重影响了程序的性能(这是非常罕见的情况!)。从功能的观点来看,唯一需要插入式智能指针的情况是,被指类的某个成员函数需要返回this,以便它可以用于另一个智能指针(事实上,也有办法使用非插入式智能指针来解决这个问题)。intrusive_ptr 不同于其它智能指针,因为它要求你来提供它所要的引用计数器。当 intrusive_ptr 递增或递减一个非空指针上的引用计数时,它是通过分别调用函数 intrusive_ptr_add_refintrusive_ptr_release来完成的。这两个函数负责确保引用计数的正确性,并且负责在引用计数降为零时删除指针。因此,你必须为你的类重载这两个函数。

 

 

下面是部分实现源代码:

非插入式版本:

namespace boost {
template class shared_ptr {
public:
template explicit shared_ptr(Y* p); //从裸露指针构造
template shared_ptr(Y* p,D d); //从裸露指针构造,同时指定删除器
~shared_ptr();
shared_ptr(const shared_ptr & r); //从另一个指针指针构造template explicit shared_ptr(const weak_ptr& r);//从弱指针构造
template explicit shared_ptr(std::auto_ptr& r);//从自动指针构造
shared_ptr& operator=(const shared_ptr& r);
void reset();//用于停止对保存指针的所有权的共享。共享资源的引用计数减一
T& operator*() const;
T* operator->() const;
T* get() const;
bool unique() const;
long use_count() const;
operator unspecified_bool_type() const; 
void swap(shared_ptr& b);
};
template
shared_ptr static_pointer_cast(const shared_ptr& r);
}
 

插入式版本(只列出了最重要的函数)

namespace boost {
  template class intrusive_ptr {
  public:
    intrusive_ptr(T* p,bool add_ref=true);
    intrusive_ptr(const intrusive_ptr& r);
    ~intrusive_ptr();
    T& operator*() const;
    T* operator->() const;
    T* get() const;
    operator unspecified-bool-type() const;
  };
  template T* get_pointer(const intrusive_ptr& p);
  template intrusive_ptr
  static_pointer_cast(const intrusive_ptr& r);

使用intrusive_ptr与使用shared_ptr相比,有两个主要的不同之处。第一个是你需要提供引用计数的机制。第二个是把this当成智能指针是合法的[12],正如我们即将看到的,有时候这样很方便。注意,在多数情况下,应该使用非插入式的 shared_ptr. 你不能用shared_ptr 来做到这一点,如果没有进行特殊处理的话,如 enable_shared_from_this.要使用 boost::intrusive_ptr, 要包含 "boost/intrusive_ptr.hpp" 并定义两个普通函数 intrusive_ptr_add_ref 和 intrusive_ptr_release. 它们都要接受一个参数,即指向你要使用intrusive_ptr的类型的指针。这两个函数的返回值被忽略。通常的做法是,泛化这两个函数,简单地调用被管理类型的成员函数去完成工作(例如,调用 add_ref 和 release)。如果引用计数降为零,intrusive_ptr_release 应该负责释放资源。以下是你应该如何实现这两个泛型函数的示范:

template void intrusive_ptr_add_ref(T* t) {
  t->add_ref();
}

template void intrusive_ptr_release(T* t) {
  if (t->release()<=0)
    delete t;
}

注意,这两个函数应该定义在它们的参数类型所在的作用域内。这意味着如果这个函数接受的参数类型来自于一个名字空间,则函数也必须定义在那里。这样做的原因是,函数的调用是非受限的,即允许采用参数相关查找,而如果有多个版本的函数被提供,那么全部名字空间肯定不是放置它们的好地方。我们稍后将看到一个关于如何放置它们的例子,但首先,我们需要提供某类的引用计数器。

 

explicit关键字:
c++中的explicit关键字用来修饰类的构造函数,表明该构造函数是显式的,既然有"显式"那么必然就有"隐式",那么什么是显示而什么又是隐式的呢?如果c++类的构造函数有一个参数,那么在编译的时候就会有一个缺省的转换操作:将该构造函数对应数据类型的数据转换为该类对象,如下面所示:
class MyClass
{
public:
   MyClass( int num );
}
....
MyClass obj = 10; //ok,convert int to MyClass
在上面的代码中编译器自动将整型转换为MyClass类对象,实际上等同于下面的操作:
MyClass temp(10);
MyClass obj = temp;
上面的所有的操作即是所谓的"隐式转换"。