前面已经学习过auto_ptr,这里补充另外一种智能指针,比auto_ptr要更强力更通用的shared_ptr。

shared_ptr

简介及使用选择 

几乎所有的程序都需要某种形式的引用计数智能指针,这种指针让我们不再需要为两个对象或更多对象共享的对象的生命周期而编写复杂的逻辑(写起来有点绕口),当被共享的对象引用计数降为0时,被共享对象被自动析构。

引用计数指针分为插入式(instrusive)和非插入式(non-instrusive)两种。前者要求它所管理的类提供明确的函数或数据成员用于管理引用计数,这要求类在设计时即预料到将与一个插入式引用计数指针一起工作,或者重新设计它。非插入式引用计数指针对它管理的类没有任何要求,引用计数智能指针拥有与它所存指针有关的内存的所有权。

引用计数智能指针可以自动管理生存周期,避免共享对象拥有者之间有太强的耦合,增加了程序的重用性。

以下场景更适合应用引用计数智能指针:类的复制很昂贵,或者它代表的有些东西必须被多个实例共享;共享的资源没有一个明确的拥有者。

引用计数智能指针的优点:引用计数智能指针可以再需要访问共享对象的多个资源之间共享访问权;引用计数智能指针还能让你把对象存入标准库的容器中而不存在泄漏的风险;如果把指针放入容器可以获得多态的好处(模板即一种静态多态计数),可以提高性能(这个不敢苟同,多态的引入更多是为了代码的重用而不是为了效率,甚至可能会牺牲部分效率),可以把相同的对象放入多个容器进行特定的查找(这是一个很好的主意,提高查找的效率并且不管生命周期)。

使用插入式引用计数指针还是非插入式引用计数指针:通常选择非插入式引用计数指针,因为他更灵活、更通用,不需要修改已有的代码。

shared_ptr的关键函数

	template <class U> explicit shared_ptr (U* p);

这个构造函数获得指定指针p的所有权,p必须是一个合法的指针,否则报错,构造完成后引用计数count=1

	template <class U, class D> shared_ptr (U* p, D del);

这个构造函数包含两个参数,p是将要被管理的指针,del是被销毁时负责释放资源的对象,被保存的对象将以del(p)的形式传给del

	template <class U> shared_ptr (const shared_ptr<U>& x) noexcept;

x中保存的资源被新构造的对象锁共享,引用计数count+1,貌似是唯一的通过构造共享对象的方式。

	template <class U> explicit shared_ptr (const weak_ptr<U>& x);

从一个weak_ptr构造shared_ptr,这使得weak_ptr的使用具有线程安全性,因为指向weak_ptr参数的共享资源引用计数将会+1(weak_ptr不影响共享资源的引用计数)。

	template <class U> shared_ptr (auto_ptr<U>&& x);

从auto_ptr中获取x保存的指针的所有权,方法是保存x保存的指针的一份拷贝并 对x调用release。

	~shared_ptr();

析构函数对引用计数count-1,如果计数为0,保存的指针被删除,删除操作采用operator delete 或者用给定的删除器对象,将指针作为唯一的参数传递给删除器对象。

operator =的重载版本

	template <class U> shared_ptr& operator= (const shared_ptr<U>& x) noexcept;

共享x中的资源

	template <class U> shared_ptr& operator= (shared_ptr<U>&& x) noexcept;

将共享资源的所有权从x转移出来,x变成一个空的shared_ptr,共享资源的引用计数不变。

想要给shared_ptr指定共享资源不能直接对指针赋值,可以调用make_shared或者reset替代。

	void reset() noexcept;

停止对保存指针所有权的共享,引用计数count-1

	template <class U> void reset (U* p);

获得p的所有权并设置引用计数为1,获得p的所有权之前默认调用析构函数,即先另之前拥有的共享对象引用计数count-1.

	element_type& operator*() const noexcept;

返回共享指针指向对象的引用。

	element_type* operator->() const noexcept;

返回保存的指针

	element_type* get() const noexcept;

返回保存的指针,当保存的指针可能为空时最好用get,而不是*或者->

	bool unique() const noexcept;

当shared_ptr是共享对象的唯一拥有者才返回为true,否则返回false

	long int use_count() const noexcept;

获取共享对象的引用计数,根据大神的建议,该函数最好只用于调试,因为引用计数的计算代价昂贵

	explicit operator bool() const noexcept;

检测保存的共享对象指针是否是一个空指针,如果是空指针就返回false,其他情况返回true。

	void swap (shared_ptr& x) noexcept;

交换两个shared_ptr保存的指针

	template <class T, class... Args>
shared_ptr<T> make_shared (Args&&... args);

make_shared的使用略绕,构造一个T类型的共享对象,args就是T类构造函数的入参,T类型的共享对象通过new创建,创建以后引用计数count=1

allocate_shared的使用类比上面。

	template <class T, class U>
shared_ptr<T> static_pointer_cast (const shared_ptr<U>& sp) noexcept;

当需要对保存的指针进行类型转换的时候调用,可以确保引用计数正确。

	template <class T, class U>
shared_ptr<T> dynamic_pointer_cast (const shared_ptr<U>& sp) noexcept;

动态转换保存的指针且确保引用计数正确

	template <class T, class U>
shared_ptr<T> const_pointer_cast (const shared_ptr<U>& sp) noexcept;

const转换保存的指针且保证引用计数正确

shared_ptr使用的时机主要是用来解决被多个对象共享的资源的正确释放机会。

大神的一个示例很能说明问题,有两个类A和B,它们共享一个int实例

     #include <cassert> 
     class A { 
       boost::shared_ptr<int> no_; 
     public: 
       A(boost::shared_ptr<int> no) : no_(no) {} 
       void value(int i) { 
         *no_=i; 
       } 
     }; 
     class B { 
       boost::shared_ptr<int> no_; 
     public: 
       B(boost::shared_ptr<int> no) : no_(no) {} 
       int value() const { 
         return *no_; 
       } 
     }; 
     int main() { 
         boost::shared_ptr<int> temp(new int(14)); 
         A a(temp); 
         B b(temp); 

a.value(28);

assert(b.value()==28);

}

类A和类B都保存了一个shared_ptr<int>,创建实例的时候,temp被传到它们的构造函数,这样同时有3个shared_ptr:a、b和temp,在例子中a、b、temp都离开main的作用域时,最后一个智能指针负责删除共享的int。

shared_ptr用标准库容器

把对象直接存入容器会有些麻烦,以值传递方式保存对象意味着调用者将获得值得一份拷贝,对于那些复制带价昂贵的类型来说可能会有性能问题,另外传值意味着没有多态的行为,如果想在容器中存放多态的对象而不想切割他们,那必须用指针,如果用裸指针,维护元素的完整性将十分复杂,使用shared_ptr可以不必担心多个使用者使用同一个元素,元素将在没有对象引用的时候被释放掉。

下面转一下大神的例子:将共享指针存入标准容器库,本来准备自己写一个例子的,可怎么样都超越不了这个经典的小例子。

class A { 
     public: 
       virtual void sing()=0; 
     protected: 
       virtual ~A() {}; 
     }; 
     class B : public A { 
     public: 
       virtual void sing() { 
         std::cout << "Do re mi fa so la"; 
       } 
     }; 
     boost::shared_ptr<A> createA() { 
       boost::shared_ptr<A> p(new B()); 
 return p; 
     } 
     int main() { 
       typedef std::vector<boost::shared_ptr<A> > container_type; 
       typedef container_type::iterator iterator; 
       container_type container; 
       for (int i=0;i<10;++i) { 
         container.push_back(createA()); 
       } 
       std::cout << "The choir is gathered: \n"; 
       iterator end=container.end(); 
       for (iterator it=container.begin();it!=end;++it) { 
         (*it)->sing(); 
       } 
     } 

这个例子的精彩之处在于同时实现了多态和共享指针的保护,多态很好理解,vector中存入的是类A的指针,通过指针实现了多态行为;共享指针的保护就堪称经典了,类A的析构函数设置为protect,这样就不能delete shared_ptr<A> get()来释放shared_ptr<A>指向的对象,手动delete释放shared_ptr<A>指向的对象将造成混乱,但类B的析构函数不是protect,shared_ptr在引用计数变为0的时候调用B的析构函数自动释放了对象。---其实这个例子里我不太明白shared_ptr是怎样正确调用B的析构函数的,内部实现肯定不是delete,也没见到传递析构器呀,问题保留,慢慢看源码分析。

shared_ptr与容器实现多态且保证安全性的一种方式:基类析构函数设置为protected。

shared_ptr与其他资源

有时候shared_ptr需要用于特殊的类型,需要其他的清理操作,而不是简单的delete,shared_ptr可以通过客户化删除器以支持这种需要,像处理FILE*这种操作系统句柄通常要用fclose来释放,这种时候我们可以定制一个客户化删除器或者传入一个单参函数来析构

class FileCloser { 
     public: 
        void operator()(FILE* file) { 
         std::cout << "The FileCloser has been called with a FILE*, " 
           "which will now be closed.\n"; 
         if (file!=0)  
           fclose(file); 
       } 
     }; 
int main() { 
       std::cout <<  "shared_ptr example with a custom deallocator.\n";  
       { 
         FILE* f=fopen("test.txt","r"); 
         if (f==0) { 
           std::cout << "Unable to open file\n"; 
           throw "Unable to open file"; 
         } 
         boost::shared_ptr<FILE>  
           my_shared_file(f, FileCloser()); 
         // 定位文件指针 
         fseek(my_shared_file.get(),42,SEEK_SET); 
       } 
       std::cout << "By now, the FILE has been closed!\n"; 
     } 

上面的例子也可以用传递单参函数的方式实现

       FILE* f=fopen("test.txt","r"); 
       if (f==0) { 
         std::cout << "Unable to open file\n"; 
         throw file_exception(); 
       } 
        
       boost::shared_ptr<FILE> my_shared_file(f,&fclose); 
       // 定位文件指针 
       fseek(&*my_shared_file,42,SEEK_SET);  
     } 
     std::cout << "By now, the FILE* has been closed!\n";

定制删除器在处理需要特殊释放程序的资源时非常有用,由于删除器不是shared_ptr的一部分,所以使用者不需要知道有关智能指针所拥有的资源的任何信息,例如使用对象池,只是简单的将对象返还到对象池中。或者单例模式singleton应该使用什么都不做的删除器。

使用定制删除器的安全性

对基类使用pretected的析构函数可以增加shared_ptr的安全性,另一个同样安全的方法是声明析构函数为pretected或者private并使用一个定制删除器来负责销毁对象,这个定制删除器必须是要删除类的友元,封装这个删除器的好方法是把删除器类实现为私有的嵌套类。

 class A { 
       class deleter { 
         public: 
           void operator()(A* p) { 
             delete p; 
           } 
 }; 
       friend class deleter; 
     public: 
       virtual void sing() { 
         std::cout << "Lalalalalalalalalalala"; 
       } 
       static boost::shared_ptr<A> createA() { 
         boost::shared_ptr<A> p(new A(),A::deleter()); 
         return p; 
       } 
     protected: 
       virtual ~A() {}; 
     }; 
     int main() { 
       boost::shared_ptr<A> p=A::createA(); 
     } 

我们不能使用普通函数作为shared_ptr<A>的工厂函数,因为嵌套的删除器是A私有的,使用这个方法,用户不能在栈上创建A的对象,也不能对A的指针调用delete。

从this生成shared_ptr

有时候需要从this获得shared_ptr,也就是希望类被shared_ptr所管理,你需要把“this”变成shared_ptr的方法,我们可以用另一个智能指针weak_ptr来解决,weak_ptr是shared_ptr的一个观察者,它只是看着它们但不影响计数,通过存储一个指向this的weak_ptr作为类成员,就可以在需要的时候获得一个指向this的shared_ptr。

当以下情况时适合使用shared_ptr

多个使用者使用同一个对象,且没有明显的拥有者

要把指针存入标准库容器时

当要传送对象到库或者从库获取对象,且没有明显的拥有者

管理一些需要特殊清除方式的资源时。 

补充

本文来自Beyond the c++ standard library中文版,有兴趣更进一步学习的朋友情参见原版。

【STL学习】智能指针之shared_ptr的更多相关文章

  1. 【C++11新特性】 C++11智能指针之shared_ptr

    C++中的智能指针首先出现在“准”标准库boost中.随着使用的人越来越多,为了让开发人员更方便.更安全的使用动态内存,C++11也引入了智能指针来管理动态对象.在新标准中,主要提供了shared_p ...

  2. C++ | 再探智能指针(shared_ptr 与 weak_ptr)

    上篇博客我们模拟实现了 auto_ptr 智能指针,可我们说 auto_ptr 是一种有缺陷的智能指针,并且在C++11中就已经被摈弃掉了.那么本章我们就来探索 boost库和C++11中的智能指针以 ...

  3. 智能指针之shared_ptr基本概述

    1.shared_ptr允许有多个指针指向同一个对象,unique_ptr独占所指向的对象. 2.类似于vector,智能指针也是模板.创建智能指针: shared_ptr<string> ...

  4. C++智能指针之shared_ptr与右值引用(详细)

    1. 介绍 在 C++ 中没有垃圾回收机制,必须自己释放分配的内存,否则就会造成内存泄露.解决这个问题最有效的方法是使用智能指针(smart pointer).智能指针是存储指向动态分配(堆)对象指针 ...

  5. 深入学习c++--智能指针(一) shared_ptr

    1. 几种智能指针 1. auto_ptr: c++11中推荐不使用他 2. shared_ptr: 每添加一次引用 就+1,减少一次引用,就-1:做到指针进行共享 3. unique_ptr: 一个 ...

  6. 智能指针(二):shared_ptr实现原理

    前面讲到auto_ptr有个很大的缺陷就是所有权的转移,就是一个对象的内存块只能被一个智能指针对象所拥有.但我们有些时候希望共用那个内存块.于是C++ 11标准中有了shared_ptr这样的智能指针 ...

  7. [5] 智能指针boost::shared_ptr

    [1]boost::shared_ptr简介 boost::shared_ptr属于boost库,定义在namespace boost中,包含头文件#include<boost/shared_p ...

  8. 关于智能指针boost::shared_ptr

    boost库中的智能指针shared_ptr, 功能强大, 且开销小,故受到广大coder的欢迎. 但在实际的使用过程中,笔者也发现了一些不足. 1.定制的删除器 shared_ptr除了可以使用默认 ...

  9. 智能指针auto_ptr & shared_ptr

    转载:智能指针auto_ptr 很多人听说过标准auto_ptr智能指针机制,但并不是每个人都天天使用它.这真是个遗憾,因为auto_ptr优雅地解决了C++设计和编码中常见的问题,正确地使用它可以生 ...

随机推荐

  1. centos安装异常解决方法

    VMware系统安装Centos7后,第一次启动出现以下异常信息: Initial setup of CentOS Linux (core) ) [x] Creat user ) [!] Licens ...

  2. Armitage主屏幕说明与命令行启动

    (1)我们将Armitage主屏幕标注为A.B和C A:该区域显示预配置的模块.您可以在模块列表下面的文本框中输入要查找的模块进行查找. B:该区域显示我们可以进行漏洞测试的活跃主机. C:该区域显示 ...

  3. WCF 下的windows服务的安装卸载

    安装:启动vs2010(如果是win2008要以管理员来启动)命令:installutil demo.exe 卸载:先在服务里停止这个服务,然后启动vs2010(如果是win2008要以管理员来启动) ...

  4. pylinter could not automatically determined the path to `lint.py`

    先关闭Sublime Text 1) 到官网先下载pylinter,http://www.logilab.org/project/pylint,然后解压缩,拷贝到C盘,目录为C:\pylint-1.0 ...

  5. HTML5学习(七)----地理定位

    参考教程:http://www.w3school.com.cn/html5/html_5_geolocation.asp 说明:设备必须有GPS定位功能才能定位的 定位用户的位置 HTML5 Geol ...

  6. Android 启动过程总结

    SystemServer的启动 frameworks/base/services/java/com/android/server/SystemServer.java: run() 其中调用Activi ...

  7. POI使用cell.getCellStyle()设置指定单元格颜色,但是其它没有指定的单元格也会变色

    HSSFCell cell = row.createCell((short)i); cell.getCellStyle().setAlignment(HSSFCellStyle.ALIGN_RIGHT ...

  8. Access增删改查 (持续更新中)

    关于Access数据库(2003)的增删改查,其实和Sql大体差不多,但是还有很多不一样的地方.下面列几个容易犯的错误:  1.Access数据库的位置: conn = new OleDbConnec ...

  9. JQuery向ashx提交中文参数方案

    客户端://异步获取数据var tpAction=action+'getSearch.ashx?key='+encodeURIComponent('中国'); $.getJSON(tpAction,f ...

  10. UPC 2224 Boring Counting ★(山东省第四届ACM程序设计竞赛 tag:线段树)

    [题意]给定一个长度为N的数列,M个询问区间[L,R]内大于等于A小于等于B的数的个数. [题目链接]http://acm.upc.edu.cn/problem.php?id=2224 省赛的时候脑抽 ...