美文网首页
第 12 章 动态内存(1)

第 12 章 动态内存(1)

作者: cb_guo | 来源:发表于2019-04-21 21:44 被阅读0次
    • 全局对象在程序启动时分配,在程序结束时销毁
    • 对于局部自动对象,当我们进入其定义所在的程序块时被创建,在离开时销毁
    • 局部 static 对象在第一次使用前分配,在程序结束时销毁

    12.1 动态内存与智能指针

    • 在C++中,动态内存的管理是通过一对运算符来完成的:new,在动态内存中为对象分配空间并返回一个指向该对象的指针,我们可以选择对对象进行初始化。delete,接受一个动态对象的指针,销毁该对象,并释放与之关联的内存。
    • 动态内存的使用很容易出现问题,因为确保在正确的时间释放内存是及其困难的。有时我们会忘记释放内存,在这种情况下会产生内存泄露;有时在尚有指针引用内存的情况下我们就释放了它,在这种情况下就会产生引用非法内存的指针。
    • 为了更容易(同时也更安全)地使用动态内存,新的标准库提供了两种智能指针类型来管理动态对象。智能指针的行为类似常规指针,重要的区别是它负责自动释放所指向的对象。新标准库提供的这两种智能指针的区别在于管理底层指针的方式;shared_ptr 允许多个指针指向同一个对象;unique_ptr 则'独占'所指向的对象。标准库还定义了一个名为 weak_ptr 的伴随类,它是一种弱引用,指向 shared_ptr 所管理的对象。这三种类型都定义在 memory 头文件中
    12.1.1 shared_ptr 类
    shared_ptr 和 unique_ptr 都支持的操作
    shared_ptr<T> 空指针,可以指向类型为 T 的对象
    unique_ptr<T>
    
    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 中

    // 指向一个值为42 的 int 的 shared_ptr
    shared_ptr<int> p3 = make_shared<int>(42);
    
    // p4 指向一个值为 "9999999999" 的 string
    shared_ptr<string> p4 = make_shared<string>(10,'9');
    
    // p5 指向一个值初始化的 int ,即值为 0 
    shared_ptr<int> p5 = make_shared<int>();
    
    // 当然,我们通常用 auto 定义一个对象来保存 make_shared 的结果,这种方式比较简单
    // p6 指向一个动态分配的空 vector<string>
    auto p6 = make_shared<vector<string>>();
    
    shared_ptr 的拷贝和赋值
    • 当进行拷贝或赋值操作时,每个 shared_ptr 都会记录有多少个其他 shared_ptr 指向相同的对象
    auto p = make_shared<int>(42);   p 指向的对象只有 p 一个引用者
    quto 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 原来指向的对象已没有引用者,会自动释放
    
    shared_ptr 自动销毁所管理的对象
    • 当指向一个对象的最后一个 shared_ptr 被销毁时,shared_ptr 类会自动销毁此对象。它通过另一个特殊的成员函数 - 析构函数来完成销毁操作
    • 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 也指向这块内存,它就不会被释放掉

    shared_ptr<Foo> use_factory(T arg){
        shared_ptr<Foo> p = factory(arg);
        // 使用 p
        return p;  // 当我们返回 p 时,引用计数进行了递增操作
    }  // p 离开了作用域,但它指向的内存不会被释放掉
    
    • 注意:如果你将 shared_ptr 存放在一个容器中,而后不再需要全部元素,而只使用其中一部分,要记得用 erase 删除不再需要的那些元素
    12.1.3 直接管理内存
    • C++语言定义了两个运算符来分配和释放动态内存。
    • 运算符 new 分配内存,delete 释放 new 分配的内存
    • 相对于智能指针,使用这两个运算符管理内存非常容易出错

    使用 new 和 delete 管理动态内存存在的三个常见问题

    • 忘记 delete 内存。忘记释放动态内存会导致人们常说的 "内存泄露" 问题,因为这种内存永远不可能被归还给自由空间了。查找内存泄露错误是非常困难的,因为通常应用程序运行很长时间后,真正耗尽内存时,才能检测到这种错误
    • 使用已经释放掉的对象。通过在释放内存后将指针置为空,有时可以检测出这种错误
    • 同一块内存释放两次。当有两个指针指向相同的动态分配对象时,可能发生这种错误。如果对其中一个指针进行了 delete 操作,对象的内存就被归还给自由空间了。如果我们随后又 delete 第二个指针,自由空间就可能被破坏
    • 注意: 相对于查找和修正这些错误来说,制造出这些错误要简单得多
    • 坚持只使用智能指针,就可以避免所有这些问题。对于一块内存,只有在没有任何智能指针指向它的情况下,智能指针才会自动释放它

    空悬指针

    delete 一个指针之后,指针值就变为无效了。虽然指针已经无效了,但在很多机器上仍然保存着(已经释放了的)动态内存的地址。在 delete 之后,指针就变成了人们所说的空悬指针,即,指向一块曾经保存数据但现在已经无效的内存的指针

    相关文章

      网友评论

          本文标题:第 12 章 动态内存(1)

          本文链接:https://www.haomeiwen.com/subject/ftktgqtx.html