zoukankan      html  css  js  c++  java
  • 第十二章 动态内存

    前言

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

    动态内存

    一. 动态内存与智能指针

    1. 新标准提供的两种智能指针区别在于,管理底层指针的方式:

    shared_ptr 允许多个指针指向同一个对象;
    unique_ptr 独占所指向的对象;

    标准库还定义了一个名为weak_ptr的伴随类,它是一种弱引用,指向shared_ptr所管理的对象;

    1. shared_ptr类

    1. 默认初始化的智能指针中保存着一个空指针;
    2. 函数unique:若指针的use_count为1,返回true,否则false;
      函数use_count:返回与指针共享对象的智能指针数量;可能很慢,主要用于调试;
    3. 最安全的分配和使用动态的方法是调用make_shared 的标准函数,通常和auto一起使用。
    4. 一旦一个shared_ptr 的计数器变为0的时候,它就会自动释放自己所管理的对象;

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

    对于一块内存,shared_ptr 类保证只要有任何shared_ptr 对象引用它,它就不会释放掉;

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

    一般而言,如果两个对象共享底层的数据,当某个对象被销毁时,我们不能单方面的销毁底层数据;

    2. 直接管理内存

    1. 默认情况下,动态内存是默认初始化的,这意味着内置类型或组合类型(比如 int ,string,vector等等)的对象的值将是未定义的,而类类型对象将用默认构造函数进行初始化;
    2. 如果提供了一个括号包围的初始化器,就可以使用auto,从此初始化器来推断想要分配的对象的类型,但是,由于编译器要用初始化器的类型推断要分配的类型,只有当括号中仅有单一初始化器时才可以使用auto。

    比如代码:

    auto p1 = new auto(1); //正确
        
    auto p2 = new auto(2, "a", 'c'); //报错
    
    1. 类似其他任何const 对象,一个动态分配的const 对象必须进行初始化;
      例如:
        // 显式初始化
        const int * p1 = new const int(12);
        // 隐式初始化
        const string * p2 = new const string;
    
    1. 默认情况下,如果new不能分配所要求的内存空间,它就会抛出一个类型为bad_alloc 的异常;
      改变new的方式来阻止它抛出的异常,即定位new:
    // 如果分配失败,不会抛异常,而是返回一个空指针
        int * p1 = new (nothrow) int;
    
    1. delete掉的必须是指向动态分配的内存,或者是一个空指针;

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

    3. 空悬指针,指向一块曾经保存数据对象但现在已经无效的内存的指针;

    避免空闲指针,可以在指针离开作用域后,释放掉所关联的内存,如果需要保留指针,可以在delete 之后将nullptr 赋予指针;

    但是即使释放自己所关联的内存,但是可能存在多个指针指向相同的内存,都变无效了,其并没有对它们进行重置,

    1. 接受指针参数的智能指针构造函数是explicit的,因此,不能将一个内置指针隐式转换为一个智能指针,必须直接初始化形式来初始化一个智能指针;
      比如:
        shared_ptr<int> p1 = new int(1024); // 报错
        
        shared_ptr<int> p2(new int(1024)); // 正确
    
    1. 使用一个内置指针来访问一个智能指针所负责的对象是很危险的,因为我们无法知道对象何时被销毁;

    2. get返回一个内置指针,指向智能指针管理的对象;

    get 用来将指针的访问权限传递给代码,你只有在确定代码不会delete 指针的情况下,才能使用get 。特别是,永远不要用get 初始化另一个智能指针或者为另一个智能指针赋值。

    1. reset函数会更新引用计数,会导致释放掉之前的指向的对象;
      reset 成员经常与unique 一起使用,来控制多个shared_ptr 共享的对象;

    2. 如果你使用智能指针管理的资源不是new分配的内存,记住传递给它一个删除器;

    3. 由于一个unique_str 拥有它所指向的对象,因此unique_str 不支持普通的或赋值操作,但是我们可以通过调用release 或 reset 将指针的所有权从一个(非const)unique_str 转移给另一个unique;

    比如:

    // 将所有权从p1转移到p2
        unique_ptr<string> p1(new string("Hello"));
        unique_ptr<string> p2(p1.release()); // 将p1置为空
        
        // 将所有权从p3转移到p2
        unique_ptr<string> p3(new string("World"));
        p2.reset(p1.release());
        
        unique_ptr<string> p4(p1); // 报错
    
    
    1. 不能拷贝 unique_ptr 的规则有一个例外:可以拷贝或赋值一个将要被销毁的 unique_ptr ,最常见的例子就是,从函数返回一个unique_ptr。

    2. weak_ptr 是一种不控制所指向对象生存期的智能指针,他指向一个 shared_ptr 管理的对象;将一个 weak_ptr 绑定到一个 shared_ptr 不会改变 shared_ptr 的引用计数。一旦最后一个指向指向对象的 shared_ptr 被销毁,对象就会释放,即使有 weak_ptr 指向对象,对象也还是会释放;

    3. use_count:共享对象的shared_ptr的数量;
      expired:若use_count 为0,返回true,否则返回false;
      lock:如果expired为true,返回一个空的shared_ptr,否则返回一个指向自己的shared_ptr;

    4. 对象可能不存在,所以不能使用weak_ptr 直接访问对象,而必须调用lock。
      比如:

    int main(int argc, const char * argv[]) {
        auto p = make_shared<int>(42);
        weak_ptr<int> wp(p); //用shared_ptr进行初始化
        
        if (shared_ptr<int> np = wp.lock()) { // 访问对象安全
            cout << "np != null" << endl;
        }
        
        return 0;
    }
    

    二. 动态数组

    1. 使用容器的类可以使用默认版本的拷贝、赋值和析构操作,分配动态数组的类则必须定义自己版本的操作,在拷贝、赋值以及销毁对象时管理所关联的内存;因此使用容器更为简单、更不容易出现内存管理错误并且可能有更好的性能;

    2. 当用new分配一个数组时,我们并未得到一个数组类型的对象,而是得到一个数组元素类型的指针;

    3. 如果初始化器数目小于元素数目,剩余元素将进行值初始化;如果初始化器数目大于元素数目,则new表达式失败,不会分配任何内存;

    4. 当用new 分配一个大小为0的数组时,new返回一个合法的非空指针;对于长度为0的数组而言,此指针就像尾后指针一样,可以像使用尾后迭代器一样使用这个指针。但是,此指针不能解引用,毕竟它不指向任何元素。

    5. 释放动态数组时,需要加上方括号[],其释放内存时,按照逆序销毁数组中元素;

    6. 标准库提供了一个可以管理new分配的数组的unique_ptr 的版本;

    代码:

    // up指向一个包含10个未初始化int的数组
    unique_ptr<int[]> up(new int[10]);
    up.release(); // 自动用delete[]销毁其指针
    
    

    当一个unique_ptr指向一个数组时,不能使用点和箭头,即不支持成员访问运算符,但是可以通过下标运算符来访问数组中元素;

    1. 和unique_ptr不同,shared_ptr不直接支持管理动态数组,如果希望使用shared_ptr管理一个动态数组,必须提供自己定义的删除器;
      必须使用get获取一个内置指针,然后用它来访问数组元素;

    2. allocator类帮助我们将内存分配和对象构造分离开;提供一种类型感知的内存分配方法,它分配的内存是原始的,未构造的;其类似vector,是个模版,必须指明可以分配的对象类型,其会根据给定的对象类型来确定恰当的内存大小和对齐位置;
      代码:

    allocator<string> alloc;
    auto const p = alloc.allocate(10); //分配10个未初始化的string
    
    1. 为了使用allocate返回的内存,必须使用construct构造对象,使用未构造的内存,其行为是未定义的;
      当用完对象后,必须对每个构造的元素调用destroy来销毁它们,函数destroy接受一个指针,对指向的对象执行析构函数;(前提是已经构造了)
      传递给deallocate的指针不能为空,它必须指向由allocate分配的内存,而且,传递给deallocate的大小参数必须与调用allocated分配内存时提供的大小参数具有一样的值。

    2. allocator类中还有定义了伴随算法,可以在未初始化内存中创建对象。

    uninitialized_copy(b, e, b2)
    uninitialized_copy_n(b, n, b2)
    uninitialized_fill(b, e, t)
    uninitialized_fill_n(b, n, t)

  • 相关阅读:
    HDU 1269 迷宫城堡
    HDU 4771 Stealing Harry Potter's Precious
    HDU 4772 Zhuge Liang's Password
    HDU 1690 Bus System
    HDU 2112 HDU Today
    HDU 1385 Minimum Transport Cost
    HDU 1596 find the safest road
    HDU 2680 Choose the best route
    HDU 2066 一个人的旅行
    AssetBundle管理机制(下)
  • 原文地址:https://www.cnblogs.com/George1994/p/6399894.html
Copyright © 2011-2022 走看看