• 动态内存和智能指针


    由编译器自动分配的内存都有着严格的生存期。全局对象在程序启动时分配,在程序结束时销毁。对于局部自动对象,当我们进入其定义所在的程序块时被创建,在离开块时销毁。局部static对象在第一次使用前分配,在程序结束时销毁。

    除了自动和static对象外,C++还支持动态分配对象。动态分配的对象的生存期与它们在哪里创建是无关的,只有当显式地被释放时,这些对象才会销毁。

    静态内存用来保存局部static对象,类static数据成员以及定义在任何函数之外的变量。栈内存用来保存定义在函数内的非static对象。分配在静态或栈内存中的对象由编译器自动创建和销毁。对于栈对象,仅在其定义的程序块运行时才存在,static对象在使用之前分配,在程序结束时销毁。

    除了静态内存和栈内存,每个程序还拥有一个内存池。这部分内存被称作自由空间或堆。程序用堆来存储动态分配的对象——即,那些在程序运行时分配的对象。动态对象的生存期由程序来控制,也就是说,当动态对象不再使用时,我们的代码必须显式地销毁它们。

    动态内存和智能指针

    在C++中,动态内存的管理是通过一对运算符来完成的:new,在动态内存中为对象分配空间并返回一个指向该对象的指针。我们可以选择对对象进行初始化;delete,接受一个动态对象的指针,销毁该对象,并释放与之关联的内存。

    动态内存的使用很容易出问题,因为确保在正确的时间释放内存是及其困难的。有时我们会忘记释放内存,在这种情况下就会产生内存泄漏;有时在尚有指针引用内存的情况下我们就释放它了,在这种情况下就会产生引用非法内存的指针。

    为了更容易(同时也安全)地使用动态内存,新的标准库提供了两种智能指针类型来管理动态对象。只能指针的行为类似常规指针,重要的区别是它负责自动释放所指向的对象。新标准库提供的这两种只能指针的区别在于管理底层指针的方式:shared_ptr允许多个指针指向同一对象;unique_ptr则“独占”所指向的对象。标准库还定义了一个名为weak_ptr的伴随类,它是一种弱引用,指向shared_ptr所管理的对象。

    shared_ptr类

    类似vector,只能指针也是模板。因此,当我们创建一个只能指针时,必须提供额外的信息——指针可以指向的类型。与vector一样,我们在尖括号内给出类型,之后是所定义的这种指针的名字:

    shared_ptr<string> p1 ; //shared_ptr,可以指向string

    shared_ptr<list<int> > p2;  //shared_ptr,可以指向int的list

    默认初始化的智能指针中保存着一个空指针。

    只能指针的使用方式与普通指针类似。解引用一个智能指针返回它所指的对象。如果在一个条件判断中使用智能指针,效果就是检测它是否为空:

    //如果p1不为空,检查它是否指向一个空string

    if(p1&&p1->empty())

      *p1="hi";

    下表列出了shared_ptr和unique_ptr都支持的操作。只适用shared_ptr的操作列入下面。

    shared_ptr和unique_ptr都支持的操作

    shared_ptr<T> sp     空智能指针,可以指向类型为T的对象

    unique_ptr<T> up    

    p          将p用作一个条件判断,若p指向一个对象,则为true

    *p        解引用p,获得它指向的对象

    p->mem     等价于(*p).mem

    p.get()      返回p中保存的指针,要小心使用,若智能指针释放了其对象,返回的指针所指向的对象也就消失了

    swap(p,q)     交换p和q中的指针

    p.swap(q) 

    shared_ptr独有的操作

    make_shared<T>(args)     返回一个shared_ptr,指向一个动态分配的类型为T的对象,使用args初始化此对象

    shared_ptr<T> p(q)       p是shared_ptr q的一个拷贝,此操作会递增q中的计数器。q中的指针必须都能转换为T*

    p=q              p和q都是shared_ptr,所保存的指针必须能相互转换。此操作会递减p的引用计数,递增q的引用计数,若p的引用计数变为0,则将其管理                的原内存释放

    p.unique()              若p.use_count()为1,返回true,否则返回false

    p.use_count()          返回与p共享对象的智能指针的数量;可能很慢,主要用于调试

    make_shared函数

    最安全的分配和使用动态内存的方法是调用一个名为make_shared的标准库函数。此函数在动态内存中分配一个对象并初始化它,返回指向对象的shared_ptr。与只能指针一样,make_shared也定义在头文件memory中。

    当要使用make_shared时,必须指定想要创建的对象的类型。定义方式与模板类相同,在函数名之后跟一个尖括号,在其中给出类型:

    复制代码
    //指向一个值为42的int的shared_ptr
    shared_ptr<int> p3=make_shared<int> (42);
    
    //p4指向一个值为"99999"的string
    shared_ptr<string> p4=make_shared<string> (5,'9');
    
    //p5指向一个值初始化的int,即,值为0
    shared_ptr<int> p5=make_shared<int> ();
    复制代码

    类似顺序容器的emplace成员,make_shared用其参数来构造给定类型的对象。例如,调用make_shared<string>时传递的参数必须与string的某个构造函数相匹配,调用make_shared<int> 时传递的参数必须能用来初始化一个int,依次类推。如果我们不传递任何参数,对象就会进行值初始化。

    当然,我们通常用auto定义一个对象来保存make_shared的结果,这种方式较简单:

    //p6指向一个动态分配的空vector<string>

    auto p6=make_shared<vector<string>> ();

    shared_ptr的拷贝和赋值

    当进行拷贝或赋值时,每个shared_ptr都会记录有多少个其他shared_ptr指向相同的对象。

    auto p=make_shard<int>(42); //p指向的对象只有p一个引用者

    auto q(p);  //p和q指向相同的对象,此对象有两个引用者

    我们可以认为每个shared_ptr都有一个关联的计数器,通常称其为引用计数。无论何时我们拷贝一个shared_ptr,计数器都会递增。例如,当用一个shared_ptr初始化另一个shared_ptr,或将它作为参数传递给一个函数以及作为函数的返回值时,它所关联的计数器都会递增,当我们给shared_ptr赋予一个新值或是shared_ptr被销毁(例如一个局部的shared_ptr离开其作用域时,计数器就会递减)

    一旦一个shared_ptr的计算器变为0,它就会自动释放自己所管理的对象:

    auto r=make_shared<int>(42);  //r指向的int只有一个引用者

    r=q;   //给r赋值,令它指向另一个地址,递增q指向的对象的引用计数,递减r原来指向的对象的引用计数,r原来指向的对象已没有引用者,会自动释放

    此例中我们分配了一个int,将其指针保存在r中。接下来,我们将一个新值赋予r。在此情况下,r是唯一执行此int的shared_ptr,在把q赋给r的过程中,此int白自动释放。

    shared_ptr自动销毁所管理的对象

    当指向一个对象的最后一个shared_ptr被销毁时,shared_ptr类会自动销毁此对象。它是通过另一个特殊的成员函数——析构函数完成销毁工作的。类似与构造函数,每个类都有一个析构函数。就像构造函数控制初始化一样,析构函数控制此类型的对象销毁时做什么操作。

    析构函数一般用来释放对象分配的资源。例如,string的构造函数(以及其他string成员)会分配内存来保存构成string的字符。string的析构函数就负责释放这些内存。类似的,vector的若干操作都会分配内存来保存其元素。vector的析构函数就负责销毁这些元素,并释放它们所占用的内存。

    shared_ptr的析构函数会递减它所指对象的引用计数。如果引用计数变为0,shared_ptr的析构函数就会销毁对象,并释放它所占用的内存。

    shared_ptr还会自动释放相关联的内存 

    当动态对象不再被使用时,shared_ptr类会自动释放动态对象,这一特性使得动态内存的使用变得非常容易。例如,我们可能有一个函数,它返回一个shared_ptr,指向一个Foo类型的动态分配的对象,对象是通过一个类型为T的参数进行初始化的:

    复制代码
    //factory返回一个shared_ptr,指向一个动态分配的对象
    shared_ptr<Foo> factory(T arg)
    {
        //恰当地处理arg
        //shared_ptr负责释放内存
        return make_shared<Foo>(arg);
    }
    复制代码

    由于factory返回一个shared_ptr,所以我们可以确保它分配的对象会在恰当的时刻被释放。例如,下面的函数将factory返回的shared_ptr保存在局部变量中:

    void use_factory(T arg)
    {
        shared_ptr<Foo> p=factory(arg);
        //使用p
        //p离开了作用域,它指向的内存会被自动释放掉
    }

    用于p是use_factory的局部变量,在use_factory结束时它将被销毁。当p被销毁时,将递减其引用计数并检查它是否为0。在此例中,p是唯一引用factory返回的内存的对象。由于p将要销毁,p指向的这个对象也会被销毁,所占用的内存会被释放。

    但如果有其他的shared_ptr也指向这块内存,它就不会被释放掉:

    void use_factory(T arg)
    {
        shared_ptr<Foo> p=factory(arg);
        //使用p
        return p; //当我们返回p时,引用计数进行了递增操作
    } //p离开了作用域,但它指向的内存不会被释放

    在此版本中,use_factory中的return语句向此函数的调用者返回一个p的拷贝。拷贝一个shared_ptr会增加所管理对象的引用计数值。现在当p被销毁时,它所指向的内存还有其他使用者。对于一块内存,shared_ptr类保证只要有任何shared_ptr对象引用它,它就不会被释放掉。

    由于在最后一个shared_ptr销毁前内存都不会释放,保证shared_ptr在无用之后不再保留就非常重要了。如果你忘记了销毁程序不再需要的shared_ptr,程序仍会正确执行,但会浪费内存。shared_ptr在无用之后仍然保留的一种可能情况是,你将shared_ptr存放在一个容器中,随后重排了容器,从而不再需要某种元素。在这种情况西下,你应该确保erase删除哪些不再需要的shared_ptr元素。

    注意:如果你将shared_ptr存放于一个容器中,而后不再需要全部元素,而只使用其中一部分,要记得用erase删除不再需要的那些元素。

    使用了动态生存期的资源的类

    程序使用动态内存处于以下三种原因之一:

    1 程序不知道自己需要使用多少个对象;

    2 程序不知到所需的准确类型

    3 程序需要在多个对象间共享数据

    容器类是处于第一种原因而使用动态内存的典型例子。

    使用动态内存的一个常见原因是运行多个对象共享相同的状态。

    智能指针学习笔记

    1. 介绍

    本文介绍智能指针的使用。智能指针是c++ 中管理资源的一种方式,用智能指针管理资源,不必担心资源泄露,将c++ 程序员 从指针和内存管理中解脱出来,再者,这也是c++发展的趋势(这话不是我说的,见《Effective c++》和《c++实践编程》),应该认真学习一下。

    智能指针中,最有名的应该数auto_ptr,该智能指针已经被纳入标准库,只需要包含<memory>头文件即可以使用,另外,TR1文档定义的shared_ptr和weak_ptr也已经实现(我用的gcc版本是gcc 4.6.1),它们的头文件是<tr1/memory> 。除此之外,还有几个好用的智能指针,不过它们属于boost库,不属于STL ,所以,用不用得到,根据自己的需要。不过,了解一下总无妨,正所谓"功不唐捐"嘛。

    下面分别介绍auto_ptr,scoped_ptr,scoped_array,shared_ptr,shared_array, weak_ptr 和 intrusive_ptr 。

    2. auto_ptr

    2.1 为什么要用智能指针

    在介绍第一个智能指针之前,先介绍下为什么要使用智能指针。先看下面这个函数:

    void f()
    {
        classA* ptr = new classA; // create an object explicitly
        ...                       // perform some operations
        delete ptr;               // clean up(destory the object explicitly)
    }

    这个函数是一系列麻烦的根源,一个显而易见的问题是,我们经常忘了delete 动作,特别是当函数中间存在return 语句时更是如此。然而,真正的麻烦发生于更隐晦之处,那就是当异常发生时,我们所要面对的灾难,异常一旦出现,函数将立刻退离,根本不会调用函数尾端的delete 语句。结果可能是内存遗失。防止这种资源遗失的常见办法就是捕捉所有异常,例如:

    void f()
    {
        classA* ptr = new classA; // create an object explicitly
        try{
        ...                       // perform some operations
        }
        catch(...){
            delete ptr; //-clean up
            throw;//-rethrow the exception
        }
    
        delete ptr;               // clean up(destory the object explicitly)
    }

    你看,为了异常发生时处理对象的删除工作,程序代码变得多么复杂和累赘!如果还有第二个对象,如果还需要更多的catch 子句,那么简直是一场恶梦。这不是优良的编程风格,复杂而且容易出错,必须尽力避免。

    如果使用智能指针,情况就会大不相同了。这个智能指针应该保证,无论在何种情形下,只要自己被摧毁,就一定要连带释放其所指资源。而由于智能型指针本身就是局部变量,所以无论是正常退出还是异常退出,只要函数退出,它就一定会被销毁。auto_ptr正是这种只能型指针。

    2.2 auto_ptr

    auto_ptr 是这样一种指针:它是"它所指向的对象"的拥有者,所以,当身为对象拥有者的auto_ptr 被摧毁时,该对象也将遭到摧毁。auto_ptr 要求一个对象只能有一个拥有者,严禁一物二主。更详细的说, auto_ptr 管理的资源必须绝对没有一个以上的auto_ptr 同时指向它。 这是因为资源的拥有者在销毁的时候,会销毁它所拥有的资源,资源不能释放两次,如果同时有两个auto_ptr拥有同一个资源,那么,在第一个auto_ptr销毁以后,第二个auto_ptr就成为野指针了,所以,任何时刻,一个资源只有一个拥有者。

    下面是上例改写后的版本:

    #include <iostream>
    #include <memory>
    using namespace std;
    
    void f()
    {
        //create and initialize an auto_ptr
        std::auto_ptr<classA> ptr(new classA);
    
        ... //perform some operations
    }

    不需要delete ,也不再需要catch 了。auto_ptr 的接口与一般指针非常相似:operator *用来提取其所指对象,operator-> 用来指向对象中的成员。然而,所有指针算法(包括++在内)都没有定义。

    注意,auto_ptr< >允许你使用一般指针惯用的赋值(assign)初始化方式。必须直接使用数值来完成除始化:

    std::auto_ptr<classA> ptr1(new classA); //OK
    std::auto_ptr<classA> ptr2 = new classA;//ERROR

    有了上面两条语句,那么下面的问题就很显然了。

    std::auto_ptr<classA> ptr;               // create an auto_ptr
    ptr = new classA;                        // ERROR
    ptr = std::auto_ptr<classA>(new classA); // ok, delete old object and own new

    2.3 auto_ptr 拥有权的转移

    上面提到过,绝对没有一个以上的auto_ptr同时指向同一个资源,那么,如果你复制(或赋值)一个auto_ptr指针会发生什么呢?发生拥有权转移,如下所示:

    //initizlize an auto_ptr with a new object
    std::auto_ptr<classA> ptr1(new classA);
    
    //copy the auto_ptr
    std::auto_ptr<classA> ptr2(ptr1);

    在第一个语句中,ptr拥有了那个new 出来的对象。在第二个语句中,拥有权由ptr1 转移到ptr2,此后,ptr2拥有那个对象,ptr1则是一个空指针。

    同理,赋值动作也会发生拥有权的转移。

    //initizlize an auto_ptr with a new object
    std::auto_ptr<classA> ptr1(new classA);
    
    //copy the auto_ptr
    std::auto_ptr<classA> ptr2;
    ptr2 = ptr1;

    在上面的语句中,如果ptr2已经拥有一个对象,则,赋值动作发生时,会调用delete,将该对象删除。

    因为auto_ptr 会发生拥有权转移问题,所以,不能完全像使用普通指针一样使用auto_ptr ,下面这个错误的用法演示的auto_ptr 的特性。

    //this is a bad example
    template <class T>
    void bad_print(std::auto_ptr<T> p)//p gets ownership of passed argument
    {
        //does p own an object?
        if (p.get() == NULL) 
        {
            std::cout << "NULL";
        }
        else
        {
            std::cout << *p;
        }
        //Oops,exiting delete the object to which p refers
    }
    
    
    int main(int argc, char const* argv[])
    {
        std::auto_ptr<int> p(new int);
        *p = 42;      // change value to which p refers
        bad_print(p); // Oops,deletes the memory to which p refers
        *p = 18;      // RUNTIME ERROR
        return 0;
    }

    我们只是想通过print函数,打印对象的值,可是,却不小心把对象给销毁了,这是非常低级的错误,再多用几次auto_ptr 以后,就不会出现这种情况了。如果我们不是通过传值,而是通过传递一个引用会怎么样呢?可以这么做,可是,你得非常小心,千万别在调用的函数里面将资源的拥有权转移了。正确的用法应该声明指针常量,如下所示:

    const std::auto_ptr<int>p(new int);
    *p = 42       // change value to which p refers
    bad_print(p); // COMPILE-TIME ERROR
    *p = 18;      // OK

    注意,auto_ptr 是一个指针,const auto_ptr 要表达的意思是"指针常量,指针不可指向其他资源,但是指针所指之物可以修改",而不是指向常量的指针。所以,const auto_ptr 类似于 T* const p而不是指向常量的指针const T* p 。下面是一个使用auto_ptr 指针的完整示例:

    #include <iostream>
    #include <memory>
    using namespace std;
    
    //define output operator for auto_ptr
    //-print object value or NULL
    
    template<class T>
    ostream& operator<< (ostream &strm, const auto_ptr<T> &p)
    {
        //does p own an object ?
        if (p.get() == NULL) 
        {
            strm << "NULL";
        }
        else
        {
            strm << *p;
        }
        return strm;
    }
    
    int main(int argc, char* argv[])
    {
        auto_ptr<int> p(new int(42));
        auto_ptr<int> q;
    
        cout << "after initizlization:" << endl;
        cout << "p : " << p << endl;
        cout << "q : " << q << endl;
    
        q = p;
        cout << "after assigning auto pointers:" << endl;
        cout << "p : " << p << endl;
        cout << "q : " << q << endl;
    
        *q += 13;
        p = q;
        cout << "after change and reassignment" << endl;
        cout << "p : " << p << endl;
        cout << "q : " << q << endl;
        return 0;
    }

    输出结果如下:

    after initizlization:
    p : 42
    q : NULL
    after assigning auto pointers:
    p : NULL
    q : 42
    after change and reassignment
    p : 55
    q : NULL

    2.4 auto_ptr 注意事项

    1. auto_ptr(以及后面介绍的std::tr1::shared_ptr) 在其析构函数内做delete,而不是delete[]动作,那意味着在动态分配而得到的array上使用auto_ptr(或tr1::shared_ptr)是一个馊主意。但是,这样的代码是可以通过编译的,所以需要用户自己留心。下面的代码就会出现用new []分配资源,用delete而不是delete[] 释放资源一样的问题。

      std::auto_ptr<std::string> aps(new std::string[10]);//资源泄漏
      std::tr1::shared_ptr<int> spi(new int[1024]);       //资源泄漏
    2. 标准容器需要元素具有可复制和可赋值的特性,而复制和赋值操作会使auto_ptr 发生所有权转移,所以,auto_ptr 不能存放在容器中

    3. scoped_ptr

    有了上面对auto_ptr 的解释,理解scoped_ptr 就没有什么难度了。scoped_ptr 的名字向读者传递了明确的信息,这个智能指针只能在本作用域中使用,不希望被转让。 scoped_ptr 通过将拷贝构造函数和operator= 函数声明为私有,以此阻止智能指针的复制,也就关闭了所有权转移的大门。

    scoped_ptr 的用法与auto_ptr 几乎一样,大多数情况下它可以与auto_ptr 相互替换,它也可用从一个auto_ptr 获得指针的管理权(同时,auto_ptr 失去管理权)

    scoped_ptr 也具有与auto_ptr 同样的"缺陷"——不能用作容器的元素,但原因不同,auto_ptr 是因为它的转移语义,而scoped_ptr 则是因为不支持拷贝和赋值,不符合容器对元素类型的基本要求。

    下面的代码演示了scoped_ptr 与auto_ptr 的区别。

    auto_ptr<int> ap(new int(10)); // 一个auto_ptr<int>
    scoped_ptr<int> sp(ap);        // 从auto_ptr 获得原始指针
    assert(ap.get() == 0);         // 原auto_ptr 不再拥有指针
    
    ap.reset(new int(20));         // auto_ptr 拥有新的指针
    cout << *ap << "," << *sp << endl;
    
    auto_ptr<int> ap2;
    ap2 = ap;              // ap2 从ap 获得原始指针,发生所有权转移
    assert(ap.get() == 0); // ap 不再拥有指针
    scoped_ptr<int> sp2;   // 另一个scoped_ptr
    sp2 = sp;              // 赋值操作,无法通过编译

    比起auto_ptr ,scoped_ptr 更明确的表达了代码原始编写者的意图:只能在定义的作用域内使用,不可转让。

    4. scoped_array

    scoped_array 与scoped_ptr 没什么区别,主要区别就是用 new[] 分配资源,用 delete [] 释放资源,而scoped_ptr 用new 分配资源,用delete 释放资源。用法如下:

    #include <iostream>
    #include <algorithm>
    #include <iterator>
    #include <boost/smart_ptr.hpp>
    using namespace std;
    using namespace boost;
    
    int main(int argc, char* argv[])
    {
        int *arr = new int[100]; //动态分配资源
        scoped_array<int> sa(arr);//用scoped_array 对象代理原始动态数组
        //scoped_array<int> sa( new int[100]);
    
        fill_n(&sa[0], 100, 5);
        sa[10] = sa[20] + sa[30];//像普通数组一样使用
    
        cout << sa[10] << "	" << sa[20] << endl;
        return 0;//在作用域最后,自动释放资源
    }

    scoped_array 与 scoped_ptr 接口和功能几乎一样,主要区别如下:

    1. 构造函数接受的指针p 必须是new [] 的结果,而不能是new 表达式的结果。
    2. 没有* , -> 操作符重载,因为scoped_array 持有的不是一个普通指针
    3. 析构函数使用delete []释放资源,而不是delete
    4. 提供operator[] 操作符重载,可以像普通数组一样使用下标访问元素
    5. 没有begin() end() 等类似容器的迭代器

    上面这个例子,可以很方便的使用vector代替,《Boost 程序库开发指南》的作者并不推荐使用scoped_array。

    5. shared_ptr

    5.1 shared_ptr 介绍

    上面已经介绍了3种智能指针,如果按照重要程度排序,auto_ptr 是最重要的,其次应该算shared_ptr 了,shared_ptr 已经被纳入标准库了,用gcc 的用户只需要#include<tr1/memory>用visual studio 08/10 的用户通过加入头文件#include<memory>即可。

    shared_ptr 是一个最像指针的"智能指针",它实现了引用计数的功能,所以,指针可以随意复制,在函数间传递,或者存储在容器里面

    shared_ptr 还有两个特有的成员函数,分别是:

    1. unique() 用于检查指针是否唯一的,如果是唯一的,就相当于auto_ptr
    2. use_count() 返回当前指针的引用计数,use_count() 不提供高效率的操作,所以,use_count() 应该仅仅用于测试或者调试。

    下面看看shared_ptr 的用法:

    #include <iostream>
    #include <tr1/memory>
    #include <assert.h>
    using namespace std;
    
    int main(int argc, char* argv[])
    {
        std::tr1::shared_ptr<int> sp( new int(10));//一个指向整数的shared_ptr
        assert( sp.unique());//现在shared_ptr 是指针的唯一持有者
    
        std::tr1::shared_ptr<int> sp2 = sp;//第二个shared_ptr ,拷贝构造函数
    
        //两个shared_ptr 相等,指向同一个对象,且引用计数为2
        assert(sp == sp2 && sp.use_count() == 2);
    
        *sp2 = 100;//使用解引用操作符修改被指对象
        assert(*sp == 100);//另一个shared_ptr 同时也被修改
    
        sp.reset();
        assert(!sp);//sp 不再持有对象
        return 0;
    }

    再看一个复杂一点的例子,用以演示智能指针作为成员变量和函数参数的情况。

    #include <iostream>
    #include <tr1/memory>
    #include <assert.h>
    using namespace std;
    using namespace std::tr1;
    
    class shared{
        private:
            shared_ptr<int> p;
        public:
        shared(shared_ptr<int> p_):p(p_){}; 
        void print()
        {
            cout << "count:" << p.use_count() << " v = " << *p << endl;
        }
    };
    
    void print_fun(shared_ptr<int> p)
    {
            cout << "count:" << p.use_count() << " v = " << *p << endl;
    }
    
    int main(int argc, char* argv[])
    {
        shared_ptr<int> p(new int(100));
        shared s1(p), s2(p);
    
        s1.print();
        s2.print();
    
        *p = 20;
        print_fun(p);
        s1.print();
    }

    输出结果如下:

    count:3 v = 100
    count:3 v = 100
    count:4 v = 20
    count:3 v = 20

    可以看到,我们不用关心shared_ptr 的具体实现,也不需要烦心它的引用计数是多少,我们只需要把它当成一个普通指针使用,再也不用担心资源泄漏。

    auto_ptr 不能一物侍二主,所以,拷贝的时候会发生所有权转移,而shared_ptr 则不存在这个问题呢,那么,把一个 auto_ptr 复制给 shared_ptr 或者把一个shared_ptr 复制给auto_ptr 会发生什么呢?答案是编译错误,即你不能这么做。

    5.2 make_shared

    前面说过,shared_ptr 是最像指针的智能指针,有了shared_ptr ,我们几乎可以抛弃delete了,但是,我们还是用到了new,用到了new 而不delete ,很不对称不是吗,所以,TR1又定义了一个小工具make_shared(类似与make_pair)来帮助我们生成对象,不过,这个功能好像还没有实现,如果,等不及要玩一下,可以用boost库,make_shared 在头文件#include<boost/make_shared.hpp>中定义,使用方法如下:

    #include <iostream>
    #include <string>
    #include <vector>
    #include <boost/make_shared.hpp>
    using namespace std;
    
    int main(int argc, char const* argv[])
    {
        boost::shared_ptr<string> sp = boost::make_shared<string>("make_shared");
        cout << *sp << endl;
    
        boost::shared_ptr< vector<int> > spv = boost::make_shared< vector<int> >(10, 2);
        cout << spv->size() << endl;
        return 0;
    }

    shared_ptr 可以应用于标注库,唯一需要牢记的是,shared_ptr 是一个指针,行为类似于普通指针,知道这一点以后,下面的代码就不难理解了。

    #include <boost/make_shared.hpp>
    #include <iostream>
    #include <vector>
    #include <boost/smart_ptr.hpp>
    using namespace std;
    using namespace boost;
    
    int main(int argc, char const* argv[])
    {
        typedef vector< shared_ptr<int> > vs;
        //声明一个持有shared_ptr 的标准容器类型,元素被初始化为空指针
        vs v(10);
    
        int i = 0;
        for (vs::iterator pos = v.begin(); pos != v.end(); ++pos) 
        {
            (*pos) = make_shared<int>(++i);//使用工厂函数(make_shared)赋值
            cout << *(*pos) << ", ";//输出值
        }
        cout << endl;
    
        shared_ptr<int> p = v[9];
    
        *p = 100;
        cout << *v[9] << endl;
        return 0;
    }

    5.3 shared_ptr 的缺陷(循环引用)

    shared_ptr 需要当心循环引用的问题,不然还是会发生资源泄漏。详细信息见这里

    6. shared_array

    我们知道scoped_ptr 和 scoped_array 的用法和区别以后,很容易猜到shared_array 的用法了。

    shared_array 类似于shared_ptr ,它包装了new[] 操作符在堆上分配的动态数组,同样,使用引用计数机制为动态数组提供了一个代理,可以在程序的生命周期里上期存在,直到没有任何引用后才释放内存。

    shared_array 的接口和功能几乎与shared_ptr 是相同的,主要区别如下:

    1. 构造函数接受指针p必须是new[] 的结果,而不是new 分配的资源
    2. 提供operator[] 操作符重载,可以像普通数组一样用下标访问
    3. 没有* -> 操作符重载,因为shared_array 持有的不是一个普通指针
    4. 析构函数使用delete[] 释放资源,而不是delete

    shared_array 用法的简单示例:

    #include <iostream>
    #include <boost/smart_ptr.hpp>
    #include <assert.h>
    using namespace std;
    using namespace boost;
    
    int main(int argc, char const* argv[])
    {
        shared_array<int> sa( new int[100]);
        shared_array<int> sa2 = sa;
    
        sa[0] = 10;
    
        cout << sa.use_count() << endl;
        cout << sa[0] << endl;
    
        assert( sa2[0] == 10);
        return 0;
    }

    7. weak_ptr

    关于weak_ptr 我是知其然,但不知其所以然。下面的说明和例子都来自《Boost 程序库完全开发指南》,无任何更改,没有理解透彻,怕改错了。

    weak_ptr 被设计为与shared_ptr 共同工作,可以从一个shared_ptr 或者另一个weak_ptr 对象构造,获得资源的观测权。但是weak_ptr 没有共享资源,它的构造函数不会引起指针引用计数的增加。同样,在weak_ptr 析构时,也不会导致引用计数减少,它只是一个静静的观察者。

    使用weak_ptr 的成员函数use_count() 可以观测资源的引用计数,另一个成员函数expired() 的功能等价于use_count() == 0,但更快,表示被观测的资源已经不复存在。

    weak_ptr 没有重载operator* 和 -> ,这是特意的,因为它不共享指针,不能操作资源,这正是它"弱"的原因,但它可以使用一个非常重要的成员函数lock() 从被观测的shared_ptr 获得一个可用的shared_ptr 对象,从而操作资源。但当expired() == ture的时候,lock()函数返回一个存储空指针的shared_ptr 。

    下面的代码示范了weak_ptr 的用法:

    shared_ptr<int> sp (new int(10)); // 一个shared_ptr
    assert(sp.use_count() == 1);
    
    weak_ptr<int> wp(sp);        // 从shared_ptr 创建weak_ptr
    assert(sp.use_count() == 1); // weak_ptr 不影响引用计数
    if (!wp.expired())           // 判断weak_ptr 观察的对象是否有效
    {
        shared_ptr<int> sp2 = wp.lock();//获得一个shared_ptr
        *sp2 = 100;
        assert(sp.use_count() == 2);
    }//退出作用域,sp2 自动析构,引用计数减1
    
    assert(sp.use_count() == 1);
    
    sp.reset();           // shared_ptr 失效
    assert(wp.expired()); // weak_ptr 将获得一个空指针
    assert(!wp.lock());

    8. intrusive_ptr

    Boost 库实现了该指针,Boost 库不推荐使用intrusive_ptr。

    9. 注意事项

    • 在资源管理类中提供对原始资源的访问(Item 15)

      使用智能指针的时候,可以通过get()成员函数,获取原始指针,从而与一下需要用到原始资源的API打交道,如果这样的API特别多,每次都写.get() 不光费时,而且不够清晰,这时,应该提供隐式类型转换。如下所示:

        class Font{
            public:
                ....
                //隐式类型转换
                operator FondHandle() const
                {return f;}
                ...
        };
    • 以独立的语句将newd 对象置入智能指针(Item 17)

    应该用独立的语句将newd 的对象置入只能指针,考虑如下调用: processWidget(std::tr1::shared_ptr(new Widget), priority());

    在上面的调用中,需要处理以下三件事:

    1. 调用priority
    2. 执行new Widget
    3. 调用tr1::shared_ptr构造函数

    c++ 编译器会以什么样的次序完成上面三件事,我们不得而知,如果调用序列如下:

    1. 执行new Widget
    2. 调用priority
    3. 调用tr1::shared_ptr构造函数

    万一对priority 的调用导致异常,则new Widget返回的指针就会遗失,我们无法使用,也无法释放该资源,所以,安全的处理方式,应该是这样的:

    std::tr1::shared_ptr<Widget> pw(new Widget);
    processWidget(pw, priority());

    10. 总结

    在上面所有介绍的智能指针中,auto_ptr ,shared_ptr 和weak_ptr 已经纳入标准库,可以放心使用,而不用担心可移植性的问题。其中auto_ptr 和shared_ptr 最为重要,shared_ptr和普通指针最为相似,不知道该用哪种类型的智能指针的时候,就用shared_ptr 。

    参考资料

  • 相关阅读:
    py计算程序运行时间-简易版
    py-冒泡排序
    py_冒泡排序
    Python测试函数运行时间
    py_二分查找
    Python发送get、post请求
    py_递归实例:汉诺塔问题
    递归实例:汉诺塔问题
    Jmeter断言实例—响应断言
    第十四天-linux命令及基础知识实战
  • 原文地址:https://www.cnblogs.com/alantu2018/p/8462391.html
Copyright © 2020-2023  润新知