• Boost智能指针使用总结


      内存管理是一个比较繁琐的问题,C++中有两个实现方案: 垃圾回收机制智能指针。垃圾回收机制因为性能等原因不被C++的大佬们推崇, 而智能指针被认为是解决C++内存问题的最优方案。

    1. 智能指针定义

         一个智能指针就是一个C++的对象, 这对象的行为像一个指针,但是它却可以在其不需要的时候自动删除。注意这个“其不需要的时候”, 这可不是一个精确的定义。这个不需要的时候可以指好多方面:局部变量退出函数作用域、类的对象被析构……。所以boost定义了多个不同的智能指针来管理不同的场景。

    shared_ptr<T> 内部维护一个引用计数器来判断此指针是不是需要被释放。是boost中最常用的智能指针了。
    scoped_ptr<t> 当这个指针的作用域消失之后自动释放
    intrusive_ptr<T> 也维护一个引用计数器,比shared_ptr有更好的性能。但是要求T自己提供这个计数器。
    weak_ptr<T> 弱指针,要和shared_ptr 结合使用
    shared_array<T> 和shared_ptr相似,但是访问的是数组
    scoped_array<T> 和scoped_ptr相似,但是访问的是数组

    2. Boost::scoped_ptr<T>

    2.1 定义

        scoped_ptr 是boost中最简单的智能指针。scoped_ptr的目的也是很简单, 当一个指针离开其作用域时候,释放相关资源。特别注意的一定就是scoped_ptr 不能共享指针的所有权不能转移所有权。也就是说这个内存地址就只能给的声明的变量用,不能给其他使用。

    2.2 特点

    (1)scoped_ptr的效率和空间的消耗内置的指针差不多

    (2)scoped_ptr不能用于管理数组对象,不能指向一块能够动态增长的内存区域(用scoped_array代替)

    (3)scoped_ptr不能转换所有权,因此不能作为函数的返回值

    (4)scoped_ptr不能共享所有权,因此不能用于stl的容器中(用shared_ptr代替)

    2.3 使用原则

    (1)在可能有异常抛出的作用域使用指针

    (2)函数里有几条控制路径

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

    (4)异常安全非常重要时(总应如此!)

    2.4 例子

     1 class test 
     2 { 
     3 public: 
     4     void print() 
     5     { 
     6         cout << "test print now" <<endl; 
     7     } 
     8 };
     9 int _tmain(int argc, _TCHAR* argv[]) 
    10 { 
    11     boost::scoped_ptr<test> x(new test); 
    12     x->print(); 
    13     return 0; 
    14 }
    View Code

    3.Boost::shared_ptr<T>

    3.1 定义

      boost::shared_ptr是可以共享所有权的智能指针.

    3.2 特点

    (1)boost::shared_ptr在内部维护一个引用计数器, 当有一个指针指向这块内存区域引用计数+1, 反之-1, 如果没有任何指针指向这块区域, 引用计数器为0,释放内存区域

    (2)boost::shared_ptr可以共享和转移所有权

    (3)boost::shared_ptr可以被标准库的容器所使用

    (4)boost::shared_ptr是线程安全的,这点在多线程程序中也非常重要

    (5)boost::shared_ptr不能指向一块动态增长的内存(用share_array代替)

    3.3 使用原则

    (1)避免对shared_ptr所管理的对象的直接内存管理操作,以免造成该对象的重释放

    (2)shared_ptr并不能对循环引用的对象内存自动管理(这点是其它各种引用计数管理内存方式的通病)

    (3)不要构造一个临时的shared_ptr作为函数的参数

    3.4 例子

    1 int _tmain(int argc, _TCHAR* argv[]) 
    2 { 
    3     boost::shared_ptr<test> ptr_1(new test); 
    4     ptr_1->print();//引用计数为1
    5     boost::shared_ptr<test> ptr_2 = ptr_1; 
    6     ptr_2->print();//引用计数为2
    7     ptr_1->print();// 引用计数还是为2
    8     return 0; 
    9 }
    View Code

    4. Boost::intrusive_ptr<T>

    4.1 定义

      boost::intrusive_ptr一种“侵入式”的引用计数指针,它实际并不提供引用计数功能,而是要被存储的对象自己实现引用计数功能,并提供intrusive_ptr_add_refintrusive_ptr_release函数接口供boost::intrusive_ptr调用。

    4.2 使用原则

    (1)你需要把 this当作智能指针来使用

    (2)已有代码使用或提供了插入式的引用计数

    (3)智能指针的大小必须与裸指针的大小相等

    4.3 例子

      下面通过一个具体的例子来说明boost::intrusive_ptr的用法,首先实现一个基类intrusive_ptr_base,定义intrusive_ptr_add_ref和intrusive_ptr_release函数来提供引用计数功能。

     1 /**
     2 * intrusive_ptr_base基类,提供intrusive_ptr_add_ref()和intrusive_ptr_release()函数来提供引用计数功能;
     3 * 使用boost::intrusive_ptr指针存储的用户类类型必须继承自intrusive_ptr_base基类。
     4 */
     5 #include <ostream>
     6 #include <boost/checked_delete.hpp>
     7 #include <boost/detail/atomic_count.hpp>
     8  
     9  
    10 template<class T>
    11 class intrusive_ptr_base {
    12 public:
    13     /**
    14     * 缺省构造函数
    15     */
    16     intrusive_ptr_base(): ref_count(0) {
    17         std::cout << "  Default constructor " << std::endl;
    18     }
    19      
    20     /**
    21     * 不允许拷贝构造,只能使用intrusive_ptr来构造另一个intrusive_ptr
    22     */
    23     intrusive_ptr_base(intrusive_ptr_base<T> const&): ref_count(0) {
    24         std::cout << "  Copy constructor..." << std::endl;
    25     }
    26      
    27     /**
    28     * 不允许进行赋值操作
    29     */
    30     intrusive_ptr_base& operator=(intrusive_ptr_base const& rhs) {
    31         std::cout << "  Assignment operator..." << std::endl;
    32         return *this;
    33     }
    34      
    35     /**
    36     * 递增引用计数(放到基类中以便compiler能找到,否则需要放到boost名字空间中)
    37     */
    38     friend void intrusive_ptr_add_ref(intrusive_ptr_base<T> const* s) {
    39         std::cout << "  intrusive_ptr_add_ref..." << std::endl;
    40         assert(s->ref_count >= 0);
    41         assert(s != 0);
    42         ++s->ref_count;
    43     }
    44  
    45     /**
    46     * 递减引用计数
    47     */
    48     friend void intrusive_ptr_release(intrusive_ptr_base<T> const* s) {
    49         std::cout << "  intrusive_ptr_release..." << std::endl;
    50         assert(s->ref_count > 0);
    51         assert(s != 0);
    52         if (--s->ref_count == 0)
    53             boost::checked_delete(static_cast<T const*>(s));  //s的实际类型就是T,intrusive_ptr_base<T>为基类
    54     }
    55      
    56     /**
    57     * 类似于shared_from_this()函数
    58     */
    59     boost::intrusive_ptr<T> self() {
    60         return boost::intrusive_ptr<T>((T*)this);
    61     }
    62      
    63     boost::intrusive_ptr<const T> self() const {
    64         return boost::intrusive_ptr<const T>((T const*)this);
    65     }
    66      
    67     int refcount() const {
    68         return ref_count;
    69     }
    70      
    71 private:
    72     ///should be modifiable even from const intrusive_ptr objects
    73     mutable boost::detail::atomic_count ref_count;
    74  
    75 };
    View Code

      用户类类型需要继承intrusive_ptr_base基类,以便具有引用计数功能。

     1 #include <iostream>
     2 #include <string>
     3 #include <boost/intrusive_ptr.hpp>
     4 #include "intrusive_ptr_base.hpp"
     5  
     6 /**
     7 * 用户类类型继承自intrusive_ptr_base,该实现方式类似于boost::enable_shared_from_this<Y>
     8 */
     9 class Connection : public intrusive_ptr_base< Connection > {
    10 public:
    11     /**
    12     * 构造函数,调用intrusive_ptr_base< Connection >的缺省构造函数来初始化对象的基类部分
    13     */
    14     Connection(int id, std::string tag):
    15         connection_id( id ), connection_tag( tag ) {}
    16  
    17     /**
    18     * 拷贝构造函数,只复制自身数据,不能复制引用计数部分
    19     */
    20     Connection(const Connection& rhs):
    21         connection_id( rhs.connection_id ), connection_tag( rhs.connection_tag) {}
    22      
    23     /**
    24     * 赋值操作,同样不能复制引用计数部分
    25     */
    26     const Connection operator=( const Connection& rhs) {
    27         if (this != &rhs) {
    28             connection_id = rhs.connection_id;
    29             connection_tag = rhs.connection_tag;
    30         }
    31          
    32         return *this;
    33     }
    34  
    35 private:
    36     int connection_id;
    37     std::string connection_tag;
    38 };
    39  
    40 int main() {
    41     std::cout << "Create an intrusive ptr" << std::endl;
    42     boost::intrusive_ptr< Connection > con0 (new Connection(4, "sss") );  //调用intrusive_ptr_add_ref()递增引用计数
    43     std::cout << "Create an intrusive ptr. Refcount = " << con0->refcount() << std::endl;
    44  
    45     boost::intrusive_ptr< Connection > con1 (con0);   //调用intrusive_ptr_add_ref()
    46     std::cout << "Create an intrusive ptr. Refcount = " << con1->refcount() << std::endl;
    47     boost::intrusive_ptr< Connection > con2 = con0;   //调用intrusive_ptr_add_ref()
    48     std::cout << "Create an intrusive ptr. Refcount = " << con2->refcount() << std::endl;
    49      
    50     std::cout << "Destroy an intrusive ptr" << std::endl;
    51  
    52     return 0;
    53 }
    View Code

    程序运行输出:
    Create an intrusive ptr
    Default constructor 
    intrusive_ptr_add_ref...
    Create an intrusive ptr. Refcount = 1
    intrusive_ptr_add_ref...
    Create an intrusive ptr. Refcount = 2
    intrusive_ptr_add_ref...
    Create an intrusive ptr. Refcount = 3
    Destroy an intrusive ptr
    intrusive_ptr_release...
    intrusive_ptr_release...
    intrusive_ptr_release...

    4.4 boost::intrusive_ptr与boost::shared_ptr区别

      使用boost::shared_ptr用户类本身不需要具有引用计数功能,而是由boost::shared_ptr来提供;使用boost::shared_ptr的一大陷阱就是用一个raw pointer多次创建boost::shared_ptr,这将导致boost::shared_ptr析构时该raw pointer被多次销毁当。即不能如下使用:

    1 int *a = new int(5);
    2 boost::shared_ptr ptr1(a);
    3 boost::shared_ptr ptr2(a);  //错误!
    View Code

      boost::intrusive_ptr完全具备boost::shared_ptr的功能,且不存在shared_ptr的问题,即可以利用raw pointer创建多个intrusive _ptr,其原因就在于引用计数的ref_count对象shared_ptr是放在shared_ptr结构里,而目标对象T通过继承intrusive_ptr_base引用计数作为T对象内部成员变量,就不会出现一个对象有两个引用计数器的情况出现。
      那么为什么通常鼓励大家使用shared_ptr,而不是intrusive_ptr呢, 在于shared_ptr不是侵入性的,可以指向任意类型的对象; 而intrusive_ptr所要指向的对象,需要继承intrusive_ptr_base,即使不需要引用计数成员也会被创建。

    4.5 结论

      如果创建新类且需要进行传递,则继承intrusive_ptr_base使用intrusive_ptr

    5. Boost::weak_ptr<T>

    5.1 定义

        weak_ptr 就是一个弱指针。weak_ptr 被shared_ptr控制, 它可以通过share_ptr的构造函数或者lock成员函数转化为share_ptr

    5.2 特点

    (1)weak_ptr的一个最大特点就是它共享一个share_ptr的内存

    (2)无论是构造还是析构一个weak_ptr都不会影响引用计数器

    5.3 弱引用与强引用

      一个强引用被引用的对象活着的话,这个引用也存在(就是说,当至少有一个强引用,那么这个对象就不能被释放)。boost::share_ptr就是强引用。相对而言,弱引用引用的对象活着的时候不一定存在。仅仅是当它存在的时候的一个引用。弱引用不修改该对象的引用计数,这意味这弱引用它并不对对象的内存进行管理,在功能上类似于普通指针,然而一个比较大的区别是,弱引用能检测到所管理的对象是否已经被释放,从而避免访问非法内存

     1 boost::weak_ptr
     2 
     3 boost::weak_ptr<T>是boost提供的一个弱引用的智能指针,它的声明可以简化如下:
     4 
     5 namespace boost {
     6 
     7     template<typename T> class weak_ptr {
     8     public:
     9         template <typename Y>
    10         weak_ptr(const shared_ptr<Y>& r);
    11 
    12         weak_ptr(const weak_ptr& r);
    13 
    14         ~weak_ptr();
    15 
    16         T* get() const; 
    17         bool expired() const; 
    18         shared_ptr<T> lock() const;
    19     }; 
    20 }
    View Code

      可以看到,boost::weak_ptr必须从一个boost::share_ptr或另一个boost::weak_ptr转换而来,这也说明,进行该对象的内存管理的是那个强引用的boost::share_ptrboost::weak_ptr只是提供了对管理对象的一个访问手段

      boost::weak_ptr除了对所管理对象基本访问功能(通过get()函数)外,还有两个常用的功能函数:expired()用于检测所管理的对象是否已经释放lock()用于获取所管理的对象的强引用指针

    5.4 循环引用

    5.4.1 循环定义

      引用计数是一种便利的内存管理机制,但它有一个很大的缺点,那就是不能管理循环引用的对象

    5.4.2 循环引用例子

     1 #include <string>
     2 #include <iostream>
     3 #include <boost/shared_ptr.hpp>
     4 #include <boost/weak_ptr.hpp>
     5 
     6 class parent;
     7 class children;
     8 
     9 typedef boost::shared_ptr<parent> parent_ptr;
    10 typedef boost::shared_ptr<children> children_ptr;
    11 
    12 class parent
    13 {
    14 public:
    15     ~parent() { std::cout <<"destroying parent
    "; }
    16 
    17 public:
    18     children_ptr children;
    19 };
    20 
    21 class children
    22 {
    23 public:
    24     ~children() { std::cout <<"destroying children
    "; }
    25 
    26 public:
    27     parent_ptr parent;
    28 };
    29 
    30 
    31 void test()
    32 {
    33     parent_ptr father(new parent());
    34     children_ptr son(new children);
    35 
    36     father->children = son;
    37     son->parent = father;
    38 }
    39 
    40 void main()
    41 {
    42     std::cout<<"begin test...
    ";
    43     test();
    44     std::cout<<"end test.
    ";
    45 }
    View Code

      运行该程序可以看到,即使退出了test函数后,由于parent和children对象互相引用,它们的引用计数都是1,不能自动释放,并且此时这两个对象再无法访问到。这就引起了c++中那臭名昭著的内存泄漏。

    一般来讲,解除这种循环引用有下面有三种可行的方法

    (1)当只剩下最后一个引用的时候需要手动打破循环引用释放对象

    (2)当parent的生存期超过children的生存期的时候,children改为使用一个普通指针指向parent

    (3)使用弱引用的智能指针打破这种循环引用

    虽然这三种方法都可行,但方法1和方法2都需要程序员手动控制,麻烦且容易出错。这里主要介绍一下第三种方法和boost中的弱引用的智能指针boost::weak_ptr。

    5.4.3 通过boost::weak_ptr来打破循环引用

      由于弱引用不更改引用计数,类似普通指针,只要把循环引用的一方使用弱引用,即可解除循环引用。对于上面的那个例子来说,只要把children的定义改为如下方式,即可解除循环引用:

    1 class children
    2 {
    3 public:
    4     ~children() { std::cout <<"destroying children
    "; }
    5 
    6 public:
    7     boost::weak_ptr<parent> parent;
    8 };
    View Code

      最后值得一提的是,虽然通过弱引用指针可以有效的解除循环引用,但这种方式必须在程序员能预见会出现循环引用的情况下才能使用,也可以是说这个仅仅是一种编译期的解决方案,如果程序在运行过程中出现了循环引用,还是会造成内存泄漏的。因此,不要认为只要使用了智能指针便能杜绝内存泄漏。毕竟,对于C++来说,由于没有垃圾回收机制,内存泄漏对每一个程序员来说都是一个非常头痛的问题。

    5.5 使用原则

    (1)要打破递归的依赖关系

    (2)使用一个共享的资源不需要共享所有权

    (3)避免悬空的指针

    (4)shared_ptr构造weak_ptr时,weak_ptr所指内存为空会抛出异常,而weak_ptr的lock()成员不会抛出异常但会返回个空指针,根据自己需求选择

    5.6 例子

     1 int _tmain(int argc, _TCHAR* argv[]) 
     2 { 
     3     boost::shared_ptr<test> sharePtr(new test);;
     4     boost::weak_ptr<test> weakPtr(sharePtr); 
     5     //weakPtr 就是用來保存指向這塊內存區域的指針的 
     6     //干了一大堆其他事情
     7     boost::shared_ptr<test> sharePtr_2 = weakPtr.lock(); 
     8     if (sharePtr_2) 
     9         sharePtr_2->print();
    10     return 0; 
    11 }
    View Code

    6. Boost::shared_array<T> 和Boost::scoped_array<T>

    6.1 定义

        前面提到过shared_ptr和scoped_ptr不能用于数组的内存(new []),所以shared_array和scoped_array就是他们的代替品。

    6.2 例子

    1 int _tmain(int argc, _TCHAR* argv[]) 
    2 { 
    3     const int size = 10; 
    4     boost::shared_array<test> a(new test[]);
    5     for (int i = 0; i < size; ++i) 
    6         a[i].print();
    7     return 0; 
    8 }
    View Code

    7.std::auto_ptr

    7.1 定义

      auto_ptr是C++标准库里的类,它接受一个类型形参的模板,为动态分配的对象提供异常安全。其实,它的核心思想是:用一个对象存储需要被自动释放的资源,然后依靠对象的析构函数来释放资源

    7.2 特点

    (1)auto_ptr的构造函数带explicit 关键字,必须使用初始化的直接形式创建auto_ptr对象。        

    1 auto_ptr<int> ap(new int(1024));  //ok   
    2 auto_ptr<int> ap=new int(1024);   //error
    View Code

    (2)auto_ptr 在析构函数中释放了动态分配的空间,因此能自动释放内存。下面函数只动态分配了内存,并没有显示释放。但是编译器保证在展开栈越过f之前运行pi的析构函数。

    1 void f() { auto_ptr<int> ap(new int(1024)); } 
    View Code

    (3)auto_ptr重载了解引用操作符箭头操作符,支持了普通指针的行为。

    (4)赋值时删除了左操作数指向的对象

    1 auto_ptr<int> ap1(new int(1024));  auto_ptr<int> ap2;
    2 ap2=ap1;
    View Code

           将ap1赋值给ap2后,删除了ap2原来指的对象;ap2置为指向ap1所指的对象;ap1为未绑定对象。可看代码。

    (5)测试auto_ptr对象,可以调用get成员函数,该函数返回包含在auto_ptr对象中的基础指针。

    1 if(ap.get())  *ap=512;   //ok          
    2 if(ap) *ap=512;             //error
    View Code

    7.3 使用原则

    尽管auto_ptr类模板为处理动态分配的内存提供了安全性和便利性的尺度,但是也存在不少缺陷,接下来结合例子给出auto_ptr的一些缺陷。

    (1)不要使用auto_ptr对象保存指向静态分配对象的指针。否则,当auto_ptr对象本身被撤销时,它将试图删除指向非动态分配对象的指针,导致未定义的行为。

    1 int a=1;
    2 auto_ptr<int> ap(&a);  //编译没有问题,会导致未定义行为
    View Code

    (2)不要使两个auto_ptr对象指向同一对象

    1 auto_ptr<int> ap1(new int (1024));
    2 auto_ptr<int> ap2(ap1.get());
    View Code

    (3)不要使用auto_ptr对象保存指向动态分配数组的指针。从源代码中可以看出,它用的是delete操作符,而不是delete [ ] 操作符

    (4)不要auto_ptr对象存储在容器中。因为auto_ptr的复制赋值具有破坏性。不满足容器要求:复制或赋值后,两个对象必须具有相同值

    7.4 例子

     1 #include <utility>
     2 #include <iostream>
     3 using namespace std;
     4  
     5 class A
     6 {
     7 public:
     8     A() { id = ++count; cout << "create A" << id  <<  "
    "; }
     9     ~A() { cout << "destroy A" << id << "
    "; }
    10 private:
    11     static int count;
    12     int id;
    13 };
    14  
    15 int A::count = 0;
    16  
    17 /*调用该函数会丢失掉所有权*/
    18 void sink(auto_ptr<A> a)
    19 {
    20     cout << "Enter sink()
    ";
    21 }
    22  
    23 /*调用该函数会创建对象,并获取所有权*/
    24 auto_ptr<A> create()
    25 {
    26     cout << "Enter create()
    ";
    27     auto_ptr<A> a(new A());
    28     return a;
    29 }
    30  
    31 int main(int argc, char *argv[])
    32 {
    33     auto_ptr<A> a1 = create();
    34     auto_ptr<A> a2 = a1; /*转移所有权,此时a1无效了*/
    35     auto_ptr<A> a3(new A());
    36     cout << "Exit create()
    ";
    37     sink(a2);/*丢失所有权,会发现a2的释放在sink函数中进行*/
    38     cout << "Exit sink()
    ";
    39     return 0;
    40 }
    41  
    42 输出结果是:<br>Enter create()<br>create A1<br>create A2<br>Exit create()<br>Enter sink()<br>destroy A1<br>Exit sink()<br>destroy A2<br><br>
    View Code

    8. 使用智能指针的几个注意点

    (1)声明一个智能指针的时候要立即给它实例化, 而且一定不能手动释放它。

    (2)…_ptr<T> 不是T* 类型。所以:

              a: 声明的时候要…_ptr<T> 而不是….._ptr<T*>。

              b:不能把T* 型的指针赋值给它。

              c: 不能写ptr=NULL, 而用ptr.reset()代替。

    (3)不能循环引用

    (4)不要声明临时的share_ptr, 然后把这个指针传递给一个函数

    原文链接:http://www.cnblogs.com/sld666666/archive/2010/12/16/1908265.html

  • 相关阅读:
    Proc、宿主变量、指示变量、数组变量、通信区sqlca,oraca ---(day07)
    Cursor、Exception、Procedure、Function、Package、Trigger(day06)
    大话设计模式C++版——建造者模式
    大话设计模式C++版——观察者模式
    大话设计模式C++版——代理模式
    大话设计模式C++版——工厂模式在COM中的典型应用
    大话设计模式C++版——抽象工厂模式
    大话设计模式C++版——工厂方法模式
    exe文件无法打开
    数据驱动编程之表驱动法
  • 原文地址:https://www.cnblogs.com/blueoverflow/p/4721202.html
Copyright © 2020-2023  润新知