zoukankan      html  css  js  c++  java
  • 动态内存和智能指针

    由编译器自动分配的内存都有着严格的生存期。全局对象在程序启动时分配,在程序结束时销毁。对于局部自动对象,当我们进入其定义所在的程序块时被创建,在离开块时销毁。局部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 程序需要在多个对象间共享数据

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

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

  • 相关阅读:
    8.10
    今日头条笔试题 1~n的每个数,按字典序排完序后,第m个数是什么?
    Gym 100500B Conference Room(最小表示法,哈希)
    CodeForces 438D The Child and Sequence(线段树)
    UVALIVE 6905 Two Yachts(最小费用最大流)
    Gym Conference Room (最小表示法,哈希)
    hdu 2389 Rain on your Parade(二分图HK算法)
    Codeforces Fox And Dinner(最大流)
    zoj 3367 Counterfeit Money(dp)
    ZOJ3370. Radio Waves(2-sat)
  • 原文地址:https://www.cnblogs.com/wuchanming/p/4053685.html
Copyright © 2011-2022 走看看