C++ 智能指针类

转自http://blog.csdn.net/heyabo/article/details/8791410

这篇文章主要来源于:codeguru网站的一篇文章:A TR1 tutorial:smart pointer  (详细介绍了C++的智能指针,尤其是shared_ptr)。

众所周知,在 TR1 之前,C++标准库中的智能指针只有auto_ptr,但由于它的【排他所有权模式】(exclusive ownership model)导致了许多问题,为解决,C++TR1中引入了 boost 开源库中的智能指针:shared_ptr 和 weak_ptr 并使之成为了标准库的一部分(C++11标准)。

注1C++ TR1 即 C++ Technical Report 1 是 ISO/IEC TR 19768, C++ Library Extensions(函数库扩充)的一般名称,它是针对 C++ 标准库的第一次扩展。

注2:C++最新标准:C++11已将智能指针:shared_ptr、weak_ptr收录为标准库中,即对应为:std::shared_ptr, std::weak_ptr,相应的头文件:<memory>(相比TR1:头文件:<tr1/memory>).

注3若读者编译器不支持C++11标准,则编译时:1.将头文件由<memory> 改为 <tr1/memory>; 2.将namespace由 std:: 改为 std::tr1 .


一、智能指针类:std::auto_ptr

 

由于 auto_ptr 基于【排他所有权模式】,这意味着:两个指针(同类型)不能指向同一个资源,复制或赋值都会改变资源的所有权。

一个简单的例子1

  1. #include <iostream>    
  2. #include <memory>    
  3. class A    
  4. {    
  5. public:    
  6.     void print(){std::cout<<"A::print"<<std::endl;}    
  7. };    
  8. int main()    
  9. {    
  10.     std::auto_ptr<A>pa1(new A);    
  11.     pa1->print();    
  12.     std::cout<<"pa1 pointer:"<<pa1.get()<<std::endl;    
  13.     
  14.     std::auto_ptr<A>pa2(pa1); //copy constructor    
  15.     pa2->print();    
  16.     std::cout<<"pa1 pointer:"<<pa1.get()<<std::endl;    
  17.     std::cout<<"pa2 pointer:"<<pa2.get()<<std::endl;    
  18.     
  19.     return 0;    
  20. }  

输出



即即经过复制构造之后,pa1所指向资源的所有权转向了pa2,而pa1变成空,二者不能同时共享该资源。

auto_ptr 主要有两大问题

  • 复制和赋值会改变资源的所有权,不符合人的直觉。
  • 在 STL 容器中无法使用auto_ptr ,因为容器内的元素必需支持可复制(copy constructable)和可赋值(assignable)。

二、C++11中新增的智能指针


新加入标准模板库(STL)的智能指针有两个:

shared_ptr:基于引用计数模型。每次有 shared_ptr 对象指向资源,引用计数器就加1;当有 shared_ptr 对象析构时,计数器减1;当计数器值为0时,被指向的资源将会被释放掉。且该类型的指针可复制和可赋值,即其可用于STL容器中。此外,shared_ptr 指针可与多态类型和不完全类型一起使用。主要缺点:无法检测出循环引用(后面会细说),如一颗树,其中既有指向孩子结点的指针又有指向父亲结点的指针,即孩子父亲相互引用。这会造成资源无法释放,从而导致内存泄露。为了 fix 这个问题,引入了另一个智能指针:weak_ptr.

weak_ptr:指向有shared_ptr 指向的资源(即其需要shared_ptr的参与,其辅助 shared_ptr 之用),但是不会导致计数。一旦计数器为0,不管此时指向资源的 weak_ptr 指针有多少,资源都会被释放,而所有的这些 weak_ptr 指针会被标记为无效状态(即 weak_ptr作为观察shared_ptr 的角色存在着,shared_ptr 不会感受到 weak_ptr 的存在)。

上一例子的shared_ptr 实现-例子2

  1. #include <iostream>  
  2. #include <memory>  
  3. class A  
  4. {  
  5. public:  
  6.     void print(){std::cout<<"A::print"<<std::endl;}  
  7. };  
  8. int main()  
  9. {  
  10.     std::shared_ptr<A>sp1(new A); //namespace: std::  
  11.     sp1->print();  
  12.     std::cout<<"sp1 pointer:"<<sp1.get()<<std::endl;  
  13.   
  14.     std::shared_ptr<A>sp2(sp1); //copy constructor  
  15.     sp2->print();  
  16.     std::cout<<"sp1 pointer:"<<sp1.get()<<std::endl;  
  17.     std::cout<<"sp2 pointer:"<<sp2.get()<<std::endl;  
  18.   
  19.     std::cout<<"count sp1:"<<sp1.use_count()<<std::endl; //get reference count  
  20.     std::cout<<"count sp2:"<<sp2.use_count()<<std::endl;  
  21.   
  22.     return 0;  
  23. }  

输出



可知:sp2创建后,sp1对资源的所有权并没有被剥夺,而是sp1 和 sp2 均指向了资源,且此时资源的引用计数为2。当两个shard_ptr 指针sp1、sp2 超过其作用域时,最后一个析构的指针将会致使资源的释放(因为引用计数为0了)。

三、智能指针类:std::tr1::shared_ptr


3.1 概念

std::shared_ptr 智能指针共享所指向的资源(所有权),即几个 shared_ptr 可同时拥有一个对象,且共享一个控制块(constrol block),包含指向资源的 shared_ptr对象个数、指向资源的 weak_ptr 对象个数以及删除器(deleter:用户自定义的用于释放资源的函数,可以默认没有)。

一个空的 shared_ptr 对象不拥有任何资源和控制块。另一方面,一个 shared_ptr 初始化为一个NULL 指针和一个控制块,这有别有空的 shared_ptr。当共享的引用计数器为0时,资源释放(delete 操作符释放,或由用户提供的 删除器 释放它)。

3.2 使用


(1)创建一个 shared_ptr 对象

常见,一个 shared_ptr 对象可由以下四种对象来构造

  • 指向任何类型 T 的指针(包括 const T),也可为指向的资源指定删除器释放它;
  • 另一个 shared_ptr 对象;
  • 一个 weak_ptr 对象;
  • 一个 auto_ptr 对象。

它们对应的构造函数如下:

  1. //1  
  2. template<class T>  
  3. explicit shared_ptr(T*);  
  4. template<class T, class D>  
  5. shared_ptr(T*, D);  
  6. //2  
  7. template<class T>  
  8. shared_ptr(const shared_ptr<T>&);  
  9. //3  
  10. template<class T>  
  11. shared_ptr(const weak_ptr<T>&);  
  12. //4  
  13. shared_ptr(const auto_ptr&);  

(2)删除器(deleter)与 get_deleter函数:

get_deleter函数返回一个指针,指向shared_ptr 的删除器,如果没有提供删除器则返回0。

例子3

  1. #include <iostream>  
  2. #include <memory>  
  3. class A  
  4. {  
  5. public:  
  6.     static A* alloc()  
  7.     {  
  8.         A* pa = new A;  
  9.         std::cout<<"a new object was created"<<std::endl;  
  10.         return pa;  
  11.     }  
  12.     static void free(A* pa)  
  13.     {  
  14.         delete pa;  
  15.         std::cout<<"A object was destroyed"<<std::endl;  
  16.     }  
  17. };  
  18. typedef void(*deleter)(A*); //define a function type pointer to free;  
  19. int main()  
  20. {  
  21.     std::shared_ptr<A> spa(A::alloc(), &A::free);//deleter: &A::free()  
  22.   
  23.     deleter* del = std::get_deleter<deleter>(spa);  
  24.     std::cout<<"get_deleter(spa)!=0 == "<<std::boolalpha<<(del!=0)<<std::endl;  
  25.     return 0;  
  26. }  

输出



(3)-> 、 * 操作符和 get 函数

shared_ptr 类重载了-> 操作符和 * 操作符,前者返回指向资源的指针;后者指向资源的引用。故无需内部指针。其原型如下:

  1. template<class T>  
  2. class shared_ptr  
  3. {  
  4. public:  
  5.     T* get() const;  
  6.     T& operator*()const;  
  7.     T* operator->()const;  
  8. };  

其中 get 函数返回指向资源的指针,基本等同于 ->操作符,且与auto_ptr 兼容。

例子4

  1. #include <iostream>  
  2. #include <memory>  
  3. class A  
  4. {  
  5. public:  
  6.     void print(){std::cout<<"A::print"<<std::endl;}  
  7. };  
  8. int main()  
  9. {  
  10.     std::shared_ptr<A> sp(new A);  
  11.     A* pa = sp.get();  
  12.     if(pa)pa->print();  
  13.     std::cout<<"-> operator: ";  
  14.     sp->print();  
  15.     std::cout<<"* operator: ";  
  16.     (*sp).print();  
  17.   
  18.     return 0;  
  19. }  

输出



(4)条件操作符(bool operator)


shared_ptr 类提供了布尔操作符,允许 shared_ptr 对象用于布尔表达式去检查是否该shared_ptr对象里的指针为NULL。

例子5

  1. #include <iostream>  
  2. #include <memory>  
  3. #include <string>  
  4. int main()  
  5. {  
  6.     std::shared_ptr<std::string> sp1;  
  7.     if(sp1)  
  8.     {  
  9.         std::cout<<"pointer in sp1 is not NULL"<<std::endl;  
  10.     }  
  11.     else  
  12.     {  
  13.         std::cout<<"pointer in sp1 is NULL"<<std::endl;  
  14.     }  
  15.   
  16.     std::shared_ptr<std::string> sp2(new std::string("hello world"));  
  17.     if(sp2)  
  18.     {  
  19.         std::cout<<"pointer in sp2 is not NULL"<<std::endl;  
  20.     }  
  21.     else  
  22.     {  
  23.         std::cout<<"pointer in sp2 is NULL"<<std::endl;  
  24.     }  
  25.   
  26.     return 0;  
  27. }  

输出



(5)交换与赋值(Swap and assignment)


I、函数原型:void swap(shared_ptr& r); //交换*this 与 r 的内容

II、赋值操作符:operator= ,重载后可将shared_ptr 或 auto_ptr 对象赋值给 shared_ptr 对象。

原型如下:

  1. template<class T>  
  2. shared_ptr& operatork=(const shared_ptr<T>&r);  
  3. template<class T>  
  4. shared_ptr& operator=(const std::auto_ptr<T>& r);  
  5. };  

例子6

  1. #include <iostream>  
  2. #include <memory>  
  3. #include <string>  
  4. void isEmpty(std::shared_ptr<std::string>& r)  
  5. {  
  6.     if(r)  
  7.     {  
  8.         std::cout<<"pointer in shared_ptr is not NULL"<<std::endl;  
  9.     }  
  10.     else  
  11.     {  
  12.         std::cout<<"pointer in shared_ptr is NULL"<<std::endl;  
  13.     }  
  14.   
  15. }  
  16. int main()  
  17. {  
  18.     std::cout<<"before swap:"<<std::endl;  
  19.     std::shared_ptr<std::string> sp1;  
  20.     std::cout<<"sp1: ";  
  21.     isEmpty(sp1);  
  22.     std::shared_ptr<std::string> sp2(new std::string("hello world"));  
  23.     std::cout<<"sp2: ";  
  24.     isEmpty(sp2);  
  25.   
  26.     sp1.swap(sp2); //swap  
  27.     std::cout<<"after swap:"<<std::endl;  
  28.     std::cout<<"sp1: ";  
  29.     isEmpty(sp1);  
  30.     std::cout<<"sp2: ";  
  31.     isEmpty(sp2);  
  32.     std::cout<<"before operator=:"<<std::endl;  
  33.     std::cout<<"sp1: "<<sp1<<", *sp1: "<<*sp1<<std::endl;  
  34.     std::cout<<"sp2: ";  
  35.     isEmpty(sp2);  
  36.     sp2 = sp1; //assignment  
  37.     std::cout<<"after operator=:"<<std::endl;  
  38.     std::cout<<"sp1: "<<sp1<<", *sp1: "<<*sp1<<std::endl;  
  39.     std::cout<<"sp2: "<<sp2<<", *sp2: "<<*sp2<<std::endl;  
  40.   
  41.     return 0;  
  42. }  

输出



(6)unique 与 use_count函数


函数原型

  1. //use_count  
  2. long use_count() const//返回所有指向共享资源的shared_ptr 指针对象的个数  
  3.   
  4. //unique()  
  5. bool unique() const//检查是否当前只有一个share_ptr 指针指向共享的资源。等价于:use_count() == 1  

例子7

  1. #include <iostream>  
  2. #include <memory>  
  3. #include <string>  
  4. int main()  
  5. {  
  6.     std::shared_ptr<std::string> sp1(new std::string("hello world"));  
  7.     std::cout<<"unique:"<<std::boolalpha<<sp1.unique()<<std::endl;  
  8.     std::cout<<"count:"<<sp1.use_count()<<std::endl;  
  9.   
  10.     std::shared_ptr<std::string> sp2(sp1);  
  11.     std::cout<<"unique:"<<std::boolalpha<<sp1.unique()<<std::endl;  
  12.     std::cout<<"sp1 count:"<<sp1.use_count()<<std::endl;  
  13.     std::cout<<"sp2 count:"<<sp2.use_count()<<std::endl;  
  14.   
  15.     return 0;  
  16. }  

输出



(7)reset函数


函数原型

  1. //std::shared_ptr::reset  
  2. void reset();  
  3.   
  4. template<class Y>  
  5. void reset(Y* ptr);  
  6.   
  7. template<class Y, class Deleter>  
  8. void reset(Y* ptr, Deleter d);   

该函数用 ptr 指针指向的资源替换掉当前shared_ptr 管理的资源,使 shared_ptr对象管理新的资源(pointered by ptr),以前资源对应的share_ptr 对象的引用计数减1。如果reset函数的参数为空,则表示*this(当前share_ptr 对象)退出共享资源。

例子8:

  1. #include <iostream>  
  2. #include <memory>  
  3. class A  
  4. {  
  5. private:  
  6.     int m_x;  
  7. public:  
  8.     explicit A(int x =0):m_x(x){}  
  9.     int getX(){return m_x;}  
  10.     int setX(int x){m_x = x;}  
  11.     void print(){std::cout<<"A::print"<<std::endl;}  
  12.     static A* alloc(int x)  
  13.     {  
  14.         A* pa = new A(x);  
  15.         std::cout<<"a new object was created"<<std::endl;  
  16.     }  
  17.     static void free(A* pa)  
  18.     {  
  19.         std::cout<<"x: "<<pa->getX();  
  20.         delete pa;  
  21.         std::cout<<",A object was destroyed"<<std::endl;  
  22.     }  
  23. };  
  24. int main()  
  25. {  
  26.     std::shared_ptr<A> sp1(new A(10),&A::free);  
  27.     std::shared_ptr<A> sp2(sp1);  
  28.     std::shared_ptr<A> sp3(sp1);  
  29.     std::cout<<"sp1 x :"<<sp1->getX()<<std::endl;  
  30.     std::cout<<"sp2 x :"<<sp2->getX()<<std::endl;  
  31.     std::cout<<"sp3 x :"<<sp3->getX()<<std::endl;  
  32.     std::cout<<"sp1 count:"<<sp1.use_count()<<std::endl;  
  33.     std::cout<<"sp2 count:"<<sp2.use_count()<<std::endl;  
  34.     std::cout<<"sp3 count:"<<sp3.use_count()<<std::endl;  
  35.   
  36.     sp1.reset();  
  37.     A* pa = new A(20);  
  38.     sp2.reset(pa,&A::free);  
  39.     std::cout<<"after reset:"<<std::endl;  
  40.     if(NULL == sp1)  
  41.     {  
  42.         std::cout<<"pointer in sp1 is NULL"<<std::endl;  
  43.     }  
  44.     //std::cout<<"sp1 x :"<<sp1->getX()<<std::endl;  
  45.     std::cout<<"sp2 x :"<<sp2->getX()<<std::endl;  
  46.     std::cout<<"sp3 x :"<<sp3->getX()<<std::endl;  
  47.     std::cout<<"sp1 count:"<<sp1.use_count()<<std::endl;  
  48.     std::cout<<"sp2 count:"<<sp2.use_count()<<std::endl;  
  49.     std::cout<<"sp3 count:"<<sp3.use_count()<<std::endl;  
  50.   
  51.     return 0;  
  52. }  

输出

 


(8)shared_ptr 在 STL 容器中的应用


由前面提到,shared_ptr 相比于 auto_ptr,可复制和赋值,故可用于 STL 容器中。

下面的例子9:将shared_ptr<int> 放入容器中,并对每个容器中的元素进行操作,如使shared_ptr 指向的int 变量 变为原来的2倍。

  1. #include <iostream>  
  2. #include <memory>  
  3. #include <vector>  
  4. #include <algorithm>  
  5. std::shared_ptr<int> double_it(const std::shared_ptr<int>& sp)  
  6. {  
  7.     *sp *= 2;  
  8.     return sp;  
  9. }  
  10. int main()  
  11. {  
  12.     std::vector<std::shared_ptr<int>> numbers;  
  13.   
  14.     numbers.push_back(std::shared_ptr<int>(new int(1)));  
  15.     numbers.push_back(std::shared_ptr<int>(new int(2)));  
  16.     numbers.push_back(std::shared_ptr<int>(new int(3)));  
  17.   
  18.     std::cout<<"initially"<<std::endl;  
  19.     for(std::vector<std::shared_ptr<int>>::const_iterator it = numbers.begin(); it != numbers.end(); it++)  
  20.     {  
  21.         std::cout<<*(*it)<<"(count = "<<(*it).use_count()<<")"<<std::endl;  
  22.     }  
  23.   
  24.     std::transform(numbers.begin(), numbers.end(), numbers.begin(), double_it);  
  25.   
  26.     std::cout<<"after transformation"<<std::endl;  
  27.     for(std::vector<std::shared_ptr<int>>::const_iterator it = numbers.begin(); it != numbers.end(); it++)  
  28.     {  
  29.         std::cout<<*(*it)<<"(count = "<<(*it).use_count()<<")"<<std::endl;  
  30.     }  
  31.   
  32.     return 0;  
  33. }  

输出



(9)shared_ptr 在类层次结构中的应用

如 D 是 B 的子类,则可用shared_ptr<B>类型的对象(基类指针)接收shared_ptr<D>类型的对象(派生类指针)。

例子10

  1. #include <iostream>  
  2. #include <memory>  
  3. #include <string>  
  4. #include <vector>  
  5.   
  6. class Item  
  7. {  
  8. private:  
  9.     std::string title_;  
  10. public:  
  11.     explicit Item(const std::string& title):title_(title){}  
  12.     virtual ~Item(){}  
  13.   
  14.     virtual std::string Description() const = 0;  
  15.     std::string getTitle()const {return title_;}  
  16. };  
  17. class Book: public Item  
  18. {  
  19. private:  
  20.     int pages_;  
  21. public:  
  22.     Book(const std::string& title, int pages):Item(title),pages_(pages){}  
  23.   
  24.     virtual std::string Description()const {return "Book: " + getTitle();}  
  25.     int getPages()const {return pages_;}  
  26. };  
  27.   
  28. class DVD: public Item  
  29. {  
  30. private:  
  31.     int tracks_;  
  32. public:  
  33.     DVD(const std::string& title, int tracks):Item(title),tracks_(tracks){}  
  34.   
  35.     virtual std::string Description() const {return "DVD: " + getTitle();}  
  36.     int getTracks()const {return tracks_;}  
  37. };  
  38. int main()  
  39. {  
  40.     std::vector<std::shared_ptr<Item>> items;  
  41.     items.push_back(std::shared_ptr<Book>(new Book("C++ Primer",745)));  
  42.     items.push_back(std::shared_ptr<DVD>(new DVD("MrVanGogh",9)));  
  43.   
  44.     for(std::vector<std::shared_ptr<Item>>::const_iterator it = items.begin(); it != items.end(); it++)  
  45.     {  
  46.         std::cout<<(*it)->Description()<<std::endl;  
  47.     }  
  48.   
  49.     return 0;  
  50. }  


输出



(10)cast 操作符


C++ 中提供了四种强制类型转换操作符static_cast, dynamic_cast, const_cast, reinterpret_cast。而关于shared_ptr 无法利用这些原始的操作符进行转换,其定义了自己的类型转换操作符:static_pointer_cast, dynamic_pointer_cast, const_pointer_cast 。

如【9】中提到的 “若 D 是 B的子类 ”,其为向上转换,但能否向下转换呢?即从 shared_ptr<B> 到 shared_ptr<D> (当然,前提是:shared_ptr<B> 已指向了一个D对象,现在要做的就是“还原它”)。


I、使用 std::dynamic_pointer_cast,可以达到目的:

  1. template<class D, class B>  
  2. shared_ptr<D> dynamic_pointer_cast(const shared_ptr<B>& r);  

该函数不会抛出任何异常(noexcept)。若执行成功( 前提: shared_ptr<B>对象 r 已经指向了一个D对象),则返回 shared_ptr<D> 共享资源的所有权,否则返回一个空对象。

例子11

  1. #include <iostream>  
  2. #include <memory>  
  3. #include <string>  
  4. class Item  
  5. {  
  6. private:  
  7.     std::string title_;  
  8. public:  
  9.     explicit Item(const std::string& title):title_(title){}  
  10.     virtual ~Item(){}  
  11.   
  12.     virtual std::string Description() const = 0;  
  13.     std::string getTitle()const {return title_;}  
  14. };  
  15. class Book: public Item  
  16. {  
  17. private:  
  18.     int pages_;  
  19. public:  
  20.     Book(const std::string& title, int pages):Item(title),pages_(pages){}  
  21.   
  22.     virtual std::string Description()const {return "Book: " + getTitle();}  
  23.     int getPages()const {return pages_;}  
  24. };  
  25.   
  26. class DVD: public Item  
  27. {  
  28. private:  
  29.     int tracks_;  
  30. public:  
  31.     DVD(const std::string& title, int tracks):Item(title),tracks_(tracks){}  
  32.   
  33.     virtual std::string Description() const {return "DVD: " + getTitle();}  
  34.     int getTracks()const {return tracks_;}  
  35. };  
  36. int main()  
  37. {  
  38.     std::shared_ptr<Item> spi(new DVD("MrVanGogh", 9));  
  39.     std::cout<<"spi counter: "<<spi.use_count()<<std::endl;  
  40.   
  41.     std::shared_ptr<Book> spb = std::dynamic_pointer_cast<Book>(spi);  
  42.     if(spb)  
  43.     {  
  44.         std::cout<<spb->getTitle()<<", "<<spb->getPages()<<std::endl;  
  45.     }  
  46.     else  
  47.     {  
  48.         std::cout<<"pointer in spb is NULL"<<std::endl;  
  49.     }  
  50.   
  51.     std::shared_ptr<DVD> spd = std::dynamic_pointer_cast<DVD>(spi);  
  52.     if(spd)  
  53.     {  
  54.        std::cout<<spd->getTitle()<<", "<<spd->getTracks()<<std::endl;  
  55.     }  
  56.     else  
  57.     {  
  58.        std::cout<<"pointer in spd is NULL"<<std::endl;  
  59.     }  
  60.   
  61.     std::cout<<"spi counter: "<<spi.use_count()<<std::endl;  
  62.     std::cout<<"spb counter: "<<spb.use_count()<<std::endl;  
  63.     std::cout<<"spd counter: "<<spd.use_count()<<std::endl;  
  64.   
  65.   
  66.     return 0;  
  67. }  

输出



II、static_pointer_cast

根据 static_cast 的知识:编译器隐式执行的任何类型转换都可以由static_cast 显示完成(如 int -> char;); 如果编译器不提供自动转换,使用 static_cast 来执行类型转换也是很有用的(如,找回存放在 void* 指针中的值)。

注意:static_cast 转换的一个特点就是:它只会生成原变量的副本,不会对原变量有任何修改。

而static_pointer_cast 工作的前提是:static_cast<T*>(r.get()) 必须是有效的。二者理念相同!

例子12

  1. #include <iostream>  
  2. #include <memory>  
  3. #include <vector>  
  4.   
  5. int main()  
  6. {  
  7.     std::vector<std::shared_ptr<void>> items;  
  8.   
  9.     std::shared_ptr<char> sp1(new char('A'));  
  10.     std::shared_ptr<int> sp2(new int(66)); //66 ASCII : 'B'  
  11.   
  12.     std::cout<<"after creating the shared_ptr"<<std::endl;  
  13.     std::cout<<"sp1 counter: "<<sp1.use_count()<<std::endl;  
  14.     std::cout<<"sp2 counter: "<<sp2.use_count()<<std::endl;  
  15.   
  16.     items.push_back(sp1);  
  17.     items.push_back(sp2);  
  18.   
  19.     std::cout<<"after adding to the vector"<<std::endl;  
  20.     std::cout<<"sp1 counter: "<<sp1.use_count()<<std::endl;  
  21.     std::cout<<"sp2 counter: "<<sp2.use_count()<<std::endl;  
  22.   
  23.     std::shared_ptr<char> spc1 = std::static_pointer_cast<char>(*(items.begin()));  
  24.     if(spc1)  
  25.     {  
  26.         std::cout<<"&spc1: "<<&spc1<<std::endl;  
  27.         std::cout<<"*spc1: "<<*spc1<<std::endl;  
  28.     }  
  29.   
  30.     std::shared_ptr<char> spc2 = std::static_pointer_cast<char>(*(items.begin()+1));  
  31.     if(spc2)  
  32.     {  
  33.         std::cout<<"&spc2: "<<&spc2<<std::endl;  
  34.         std::cout<<"*spc2: "<<*spc2<<std::endl;  
  35.     }  
  36.     std::shared_ptr<short> spd2 = std::static_pointer_cast<short>(*(items.begin()+1));  
  37.     if(spd2)  
  38.     {  
  39.         std::cout<<"&spd2: "<<&spd2<<std::endl;  
  40.         std::cout<<"*spd2: "<<*spd2<<std::endl;  
  41.     }  
  42.     std::cout<<"after casting"<<std::endl;  
  43.     std::cout<<"sp1 couner: "<<sp1.use_count()<<std::endl;  
  44.     std::cout<<"spc1 couner: "<<spc1.use_count()<<std::endl;  
  45.     std::cout<<"sp2 couner: "<<sp2.use_count()<<std::endl;  
  46.     std::cout<<"spc2 couner: "<<spc2.use_count()<<std::endl;  
  47.     std::cout<<"spd2 couner: "<<spd2.use_count()<<std::endl;  
  48.   
  49.     return 0;  
  50. }  

输出



III、const_pointer_cast

若 const_cast<T*>(sp.get()) 返回一个NULL指针,则 std::cosnt_pointer_cast 返回一个空的对象。否则,它返回一个shared_ptr<T>对象。
原型:

  1. template<classT,class U>  
  2. shared_ptr<T>cosnt_pointer_cast(const shared_ptr<U>& r);  

四、智能指针:std::tr1::weak_ptr


引入:由于 shared_ptr 智能指针的缺陷:无法检测循环引用(关于 share_ptr 的循环引用详细内容,可参见我的另一篇文章:Effective shared_ptr)---这是所有引用型智能指针的硬伤。为了解决这个问题又引入了 weak_ptr 指针。

概念:该类型智能指针也是指向由 shared_ptr 所指向的资源,但是不增加引用计数,故称为”弱引用(weak reference)“ 。当最后拥有资源的最后一个 shared_ptr 对象离开其作用域时,资源被释放,weak_ptr 被标记为无效状态。并且可以通过函数 expired() 来检查是是否 weak_ptr 处于无效状态。weak_ptr 在访问所引用的对象前必须先转换为 shared_ptr, 即其【来于 shared_ptr 也去于 shared_ptr】。

作用(观察者的角度):weak_ptr 是用来表达临时所有权的概念:当某个对象只有存在时才需要被访问,而且随时可以被他人删除时,可以使用 weak_ptr 来跟踪该对象。需要获得临时所有权时,则将其转换为 shared_ptr ,此时若原来的 shared_ptr 被销毁,则该对象的生命周期将延长至这个临时生成的 shared_ptr 同样被销毁为止。

线程安全:考虑到多线程环境下,可通过std::weak_ptr::lock函数创建一个新的shared_ptr 管理对象的共享所有权-----作为临时访问的shared_ptr(此时引用计数是会增1 的,即此临时的 shared_ptr 若没有离开其作用域,共享的资源是不会被释放的)。如果没有管理的对象,即*this 为空,则返回的 shared_ptr 也是空;否则返回:shared_ptr<T>(*this) 。

例子13

  1. #include <iostream>  
  2. #include <memory>  
  3.   
  4. void show(const std::weak_ptr<int>& wp)  
  5. {  
  6.     std::shared_ptr<int> sp = wp.lock();  
  7.     std::cout<<*sp<<std::endl;  
  8. }  
  9. int main()  
  10. {  
  11.     std::weak_ptr<int> wp;  
  12.   
  13.     { //create a namesapce deliberately  
  14.         std::shared_ptr<int> sp(new int(10));  
  15.         wp = sp;  
  16.   
  17.         show(wp);  
  18.     }  
  19.   
  20.     std::cout<<"expire: "<<std::boolalpha<<wp.expired()<<std::endl;  
  21.   
  22.     return 0;  
  23. }  

输出



由于shared_ptr智能指针存在缺陷,故用好它也是很关键的问题,具体内容续集:Effective shared_ptr


这篇文章本是作为:C++ 智能指针类的第二部分,但无奈那篇篇幅已经不能再长了,于是只好将其单独写成一篇,且把 shared_ptr 的循环引用放在这里写,这样稍微比较连贯一些。


一、shared_ptr 的循环引用


定义:所谓循环引用,可类比于这样的一棵树,它含有父亲结点指向孩子结点的指针,也有孩子结点指向父亲结点的指针,即父亲结点与孩子结点互相引用。

可先看一个例子(改编自:智能指针的死穴---循环引用):

  1. #include <iostream>  
  2. #include <memory>  
  3. using namespace std;  
  4. class B;  
  5. class A  
  6. {  
  7. public:  
  8.     A(){cout<<"A constructor"<<endl;}  
  9.     ~A(){cout<<"A destructor"<<endl;}  
  10.     shared_ptr<B> m_b;  
  11. };  
  12.   
  13. class B  
  14. {  
  15. public:  
  16.     shared_ptr<A> m_a;  
  17.     B(){cout<<"B constructor"<<endl;}  
  18.     ~B(){cout<<"B destructor"<<endl;}  
  19. };  
  20.   
  21. int main()  
  22. {  
  23.     cout<<"shared_ptr cycle reference:\n";  
  24.     shared_ptr<A> a(new A);  
  25.     shared_ptr<B> b(new B);  
  26.     a->m_b = b; //cycle reference  
  27.     b->m_a = a;  
  28.   
  29.     return 0;  
  30. }  

输出



由输出结果可以看出:A 和 B 的析构函数都是没有执行的,内存泄露!

分析:众所周知,new 出来的对象,必须由程序员自己 delete 掉,在此运用了智能指针:shared_ptr来指向 new A,即现在 delete 的责任落到了 shared_ptr 的身上(在其退出作用域时)。但是分析下上面的代码:b 先出作用域(析构顺序与构造相反),B 的引用计数减为1但不为0,故堆上B的空间没有释放,此时的结果是:b 走了,但是 new B 并没有被 delete 掉,好吧,现在只有等待 a 来delete了。然后是 a 退出其作用域,A 的引用计数减少为1,也不为0,因为B中的 m_a指向它,结果是:a 走了,但是 new A 并没有被 delete 掉,而此时已经没有 share_ptr 对象可以将他们delete掉了,不对,好像还有:存在于new 出来的A和B对象里,如果没有delete,他俩就不会超出作用域,它们在等待delete,而 delete 却在等待 shared_ptr 对象自身发出delete,矛盾产生,于是就这样死锁了!!!故 new 出 来的 A 和 B 就这样的被遗弃,从而内存泄露了。

原因(1)new 出来的对象必须手动delete掉;(2)掌握delete的shared_ptr 在 new 出来的对象之中;(3)两个new 对象里的shared_ptr 互相等待。

解锁:试想如果只有单向指向,如上代码:去掉一行:b->m_a =a ;,但是将 B 引用 A 的信息保存在某处,且对于 A 和 B的shared_ptr  对象是不可见的,但是这些信息却可以观察到 指向 A 和 B 的 shared_ptr 对象的行为。再来分析一下:b 先出作用域,B的引用计数减少为1,不为0,此时 堆上 B 的空间没有释放,结果依旧:b 走了,但是 new B 并没有被 delete 掉。然后是 a 退出作用域,注意:此时 A 的引用计数减少为0,资源A 被释放,这也导致A 空间中的指向资源B shared_ptr对象超出作用域,从而 B的引用计数减少为0,释放B,如此 A 和 B 均能正确的释放了,这应该就是weak_ptr 智能指针的原型了。

再来看下原来的例子(加入了 weak_ptr):

  1. #include <iostream>  
  2. #include <memory>  
  3. using namespace std;  
  4. class B;  
  5. class A  
  6. {  
  7. public:  
  8.     A(){cout<<"A constructor"<<endl;}  
  9.     ~A(){cout<<"A destructor"<<endl;}  
  10.     shared_ptr<B> m_b;  
  11. };  
  12.   
  13. class B  
  14. {  
  15. public:  
  16.     weak_ptr<A> m_a;  
  17.     B(){cout<<"B constructor"<<endl;}  
  18.     ~B(){cout<<"B destructor"<<endl;}  
  19. };  
  20.   
  21. int main()  
  22. {  
  23.     cout<<"shared_ptr cycle reference:\n";  
  24.     shared_ptr<A> a(new A);  
  25.     shared_ptr<B> b(new B);  
  26.     cout<<"a counter: "<<a.use_count()<<endl;  
  27.     cout<<"b counter: "<<b.use_count()<<endl;  
  28.     a->m_b = b;   
  29.     b->m_a = a;  
  30.   
  31.     cout<<"a counter: "<<a.use_count()<<endl;  
  32.     cout<<"b counter: "<<b.use_count()<<endl;  
  33.   
  34.     cout<<"b->m_a counter: "<<b->m_a.use_count()<<endl; //that is the reference counts of A  
  35.     cout<<"expired: "<<std::boolalpha<<b->m_a.expired()<<endl;  
  36.   
  37.     return 0;  
  38. }  

输出



可见:此时 A 和 B 都成功地析构了。


二、shared_ptr 的重复析构


在shared_ptr 中看到【重复析构】这个词,其实有点诧异,因为 share_ptr 不正是由于普通指针(raw pointer)可能的内存泄露和重复析构而提出的嘛,怎么自身还有重蹈覆辙呢?

原因就在于,很多时候没有完全使用 shared_ptr ,而是普通指针和智能指针混搭在一起,或是很隐蔽地出现了这样情况,都会导致重复析构的发生。

场景1---最简单地混搭

  1. int* pInt = new int(10);  
  2. shared_ptr<int> sp1(pInt);  
  3. ...  
  4. shared_ptr<int>sp2(pInt);  

由 shared_ptr 的构造函数以及其源码(关于 shared_ptr 源码可见: std::tr1::shared_ptr源码  和 shared_ptr源码解读 ):

  1. //constructor  
  2. template<class T>  
  3. explicit shared_ptr(T* ptr);  
  4. ...  
  5. //tr1::shared_ptr   source code  
  6. ...  
  7. public:  
  8.     shared_ptr(T* p = NULL)  
  9.     {  
  10.          m_ptr = p;  
  11.          m_count = new sp_counter_base(1, 1);  
  12.          _sp_set_shared_from_this(this, m_ptr);    
  13.      }  
  14. ...  

根据 shared_ptr 的源码 可知 :此时, 普通指针构造出来的 shared_ptr(包括引用计数和控制块),其将 新生成一个引用计数类 (new sp_counter_base(1, 1)
)引用计数 初始化 1 。如果后面再有一个此类的构造函数(对同一个普通指针),则又会重新构造出一个 引用计数类,并且是引用计数初始化为1(而不是加1变成2)。这样就会被误以为存在两个shared_ptr对象,从而导致后期的重复析构了。

场景2---与 this 指针的混搭

  1. #include <iostream>  
  2. #include <memory>  
  3. using namespace std;  
  4.   
  5. class A  
  6. {  
  7. private:  
  8. public:  
  9.     A(){cout<<"constructor"<<endl;}  
  10.     ~A(){cout<<"destructor"<<endl;}  
  11.     shared_ptr<A> sget()  
  12.     {  
  13.         shared_ptr<A> sp(this);  
  14.         cout<<"this: "<<this<<endl;  
  15.         return sp;  
  16.     }  
  17. };  
  18.   
  19. int main()  
  20. {  
  21.     shared_ptr<A> test (new A);  
  22.     shared_ptr<A> spa = test->sget();  
  23.   
  24.     cout<<"spa: "<<spa<<endl;  
  25.     cout<<"test: "<<test<<endl;  
  26.     cout<<"spa counter: "<<spa.use_count()<<endl;  
  27.     cout<<"test counter: "<<test.use_count()<<endl;  
  28.   
  29.     return 0;  
  30. }  

输出



程序出现【core dumped】,根据程序crash之前的信息可知

A 对象析构的两次,原因在于 sget()函数内部的 临时shared_ptr 对象 sp 是由普通指针this 构造而来,故生成的shared_ptr 对象将生成一个新的引用计数类(不同于test的),并初始化计数为1。这将导致 test 和 spa 退出各自作用域时均执行 A 的析构函数,析构两次。

解决办法:C++11中提供了 enable_from_shared_this 类,其他类可继承它,并使用 shared_from_this方法获得类对象的shared_ptr智能指针,此时使用的引用计数类一样(具体实现与weak_ptr类有关,详情可参见shared_from_this源码)。

(1)让 A继承 enable_from_shared_this 类

(2)修改 sget 函数,调用 shared_from_this方法获得类对象的shared_ptr版本

  1. #include <iostream>  
  2. #include <memory>  
  3. using namespace std;  
  4.   
  5. class A :public enable_shared_from_this<A>  
  6. {  
  7. private:  
  8. public:  
  9.     A(){cout<<"constructor"<<endl;}  
  10.     ~A(){cout<<"destructor"<<endl;}  
  11.     shared_ptr<A> sget()  
  12.     {  
  13.         return shared_from_this();  
  14.     }  
  15. };  
  16.   
  17. int main()  
  18. {  
  19.     shared_ptr<A> test (new A);  
  20.     shared_ptr<A> spa = test->sget();  
  21.   
  22.     cout<<"spa: "<<spa<<endl;  
  23.     cout<<"test: "<<test<<endl;  
  24.     cout<<"spa counter: "<<spa.use_count()<<endl;  
  25.     cout<<"test counter: "<<test.use_count()<<endl;  
  26.   
  27.     return 0;  
  28. }  

输出



此时只析构一次,且test和spa的引用计数为同一引用计数类,值均为2.



  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值