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

    一、介绍

    全局对象在程序启动时分配,在程序结束时销毁。

    对于局部自动对象,当我们进入其定义所在的程序块时被创建,在离开块时销毁。

    局部static对象在第一次使用前分配,在程序结束时销毁。

    动态分配的对象的生存期与它们在哪里创建是无关的,只有当显式地被释放时,这些对象才会销毁。

    静态内存用来保存局部static对象、类static数据成员以及定义在任何函数之外的变量。

    栈内存用来保存定义在函数内的非static对象,分配在静态或栈内存中的对象由编译器自动创建和销毁。对于栈对象,仅在其定义的程序块运行时才存在;static对象在使用之前分配,在程序结束时销毁。

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

    二、使用

    动态内存的关联:newdelete

    new:在动态内存中为对象分配空间并返回一个指向该对象的指针。

    delete:接受一个动态对象的指针,销毁该对象,并释放与之关联的内存。

    智能指针

    负责自动释放所指向的对象。

    shared_ptr允许多个指针指向同一个对象;

    unique_ptr则独占所指向的对象。

    weak_ptr是弱引用,指向shared_ptr所管理的对象。

    这三种类型都定义在memory头文件中。

    三、shared_ptr

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

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

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

    // 如果p1不为空,检查它是否指向一个空string
    if (p1 && p1->empty()) {
        *p1 = "hi";		// 如果p1指向一个空string, 解引用p1, 将一个新值赋予string
    }
    
    shared_ptrunique_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,指向IG动态分配的类型为T的对象。使用args初始化此对象
    shared_ptr<T>p(q) p是shared_ptr q的拷贝:此操作会递增q中的计数器。q中的指针必须能转换为 T*
    p = q p和q都是shared_ptr,所保存的指针必须能相互转换。次操作会递减p的引用计数,递增q的引用计数;若q的引用计数变为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指向一个值为 "99999999"的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的拷贝和赋值
    auto p = make_shared<int>(42);	// p指向的对象只有p一个引用者
    auto q(p);						// p和q指向相同对象,此对象有两个引用者
    
    auto r = make_shared<int>(42);	// r指向的int只有一个引用者
    r = q;							// 给r赋值,令它指向另一个地址
    								// 递增q指向的对象的引用计数
    								// 递减r原来指向的对象的引用计数
    								// r原来指向的对象已没有引用者, 会自动释放
    
    shared_pt自动销毁

    当指向一个对象的最后一个shared_ptr被销毁时,shared_ptr类会自动销毁此对象。

    它是通过另一个特殊的成员函数————析构函数完成销毁工作的。类似于构造函数,每个类都有一个析构函数

    析构函数一般用来释放对象所分配的资源。

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

    当动态对象不再被使用时,shared_ptr类会自动释放动态对象。这一特性使得动态内存的使用变得非常容易。

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

    1. 程序不知道自己需要使用多少对象
    2. 程序不知道所需对象的准确类型
    3. 程序需要在多个对象间共享数据

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

    四、直接管理内存

    C++语言定义了两个运算符来分配和释放动态内存。运算符new分配内存,delete释放new分配的内存。

    在自由空间分配的内存是无名的,因此new无法为其分配的对象命名,而是返回一个指向该对象的指针:

    int *pi = new int;	// pi指向一个动态分配的、未初始化的无名对象
    string *ps = new string;  // 初始化为空string
    int *pi = new int;			// pi指向一个未初始化的int
    
    int *pi = new int(1024);	// pi指向的对象的值为1024
    string *ps = new string(10, '9');  // *ps 为 9999999999
    
    // vector有10个元素, 值依次从0到9
    vector<int> *pv = new vector<int>{0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
    

    如果我们提供了一个括号包围的初始化器,就可以使用auto从此初始化器来推断我们想要分配的对象的类型。

    auto p1 = new auto(obj);		// p指向一个与obj类型相同的对象
    								// 该对象用obj进行初始化
    auto p2 = new auto{a, b, c};	// 错误:括号中只能有单个初始化器
    
    内存耗尽

    一旦一个程序用光了它所有可用的内存,new表达式就会失败。默认情况下,如果new不能分配所要求的内存空间,它会抛出一个类型为bad_alloc的异常。我们可以改变使用new的方式来阻止它抛出异常:

    // 如果分配失败,new返回一个空指针
    int *p1 = new int;		// 如果分配失败,new抛出std::bad_alloc
    int *p2 = new (nothrow) int;	// 如果分配失败, new返回一个空指针
    

    我们称这种形式的new为定位new。定位new表达式允许我们向new传递额外的参数。在此例中,我们传递给它一个由标准库定义的名为nothrow的对象。如果将nothrow传递给new,我们的意图是告诉它不能抛出异常。如果这种形式的new不能分配所需内存,它会返回一个空指针。bad_allocnothrow都定义在头文件new中。

    释放动态内存

    为了防止内存耗尽,在动态内存使用完毕后,必须将其归还给系统。我们通过delete表达式来将动态内存归还给系统。delete表达式接受一个指针,指向我们想要释放的对象:

    delete p;	// p必须指向一个动态分配的对象或是一个空指针
    

    new类型类似,delete表达式也执行两个动作:销毁给定的指针指向的对象:释放对应的内存。

    指针值和delete

    我们传递给delete的指针必须指向动态分配的内存,或者是一个空指针。释放一块并非new分配的内存,或者将相同的指针值释放多次,其行为是未定义的:

    int i, *pil = &i, *pi2 = nullptr;
    double *pd = new double(33), *pd2 = pd;
    delete i;	// 错误: i不是一个指针
    delete pil;	// 未定义:pil 指向一个局部变量
    delete pd;	// 正确
    delete pd2;	// 未定义:pd2指向的内存已经被释放了
    delete pi2;	// 正确:释放一个空指针总是没有错误的
    

    虽然一个const对象的值不能被改变,但它本身是可以被销毁的。如同任何其他动态对象一样,想要释放一个const动态对象,只要delete指向它的指针即可:

    const int *pci = new const int(1024);
    delete pci;		// 正确:释放一个const对象
    
    动态对象的生存期直到被释放时为止

    shared_ptr管理的内存在最后一个shared_ptr销毁时会被自动释放。但对于通过内置指针类型来管理的内存,就不是这样了。对于一个由内置指针管理的动态对象,直到被显式释放之前它都是存在的。

    返回指向动态内存的指针(而不是智能指针)的函数给其调用者增加了一个额外负担——调用者必须记得释放内存:

    // factory返回一个指针,指向一个动态分配的对象
    Foo* factory(T arg) {
        // 视情况处理arg
        return new Foo(arg);  // 调用者负责释放此内存
    }
    

    与类类型不同,内置类型的对象被销毁时什么也不会发生。特别是,当一个指针离开其作用域时,它所指向的对象什么也不会发生。如果这个指针指向的是动态内存,那么内存将不会被自动释放。

    由内置指针(而不是智能指针)管理的动态内存在被显式释放前一直都会存在。

  • 相关阅读:
    bootstrap-datetimepicker使用记录
    Highcharts使用====一些问题记录
    值类型 引用类型
    java 发送邮件
    包括post,get请求(http,https)的HttpClientUtils
    整理的java的日期DateUtil
    oracle随机取数据
    oracle查询表的索引
    有关dwr推送的笔记
    java 超经漂亮验证码
  • 原文地址:https://www.cnblogs.com/huaibin/p/15320195.html
Copyright © 2011-2022 走看看