Dynamic Memory

动态内存

  • 静态内存用于保存局部static对象、类的static成员以及定义在任何函数之外的变量。栈内存用于保存定义在函数内的非static变量。
  • 除了静态内存与栈内存,每个程序还拥有一个内存池,这部分内存被称为自由空间(free store)或(heap)。

动态内存与智能指针

  • 在C++中,动态内存的管理通过 一对运算符完成:new,在动态内存中为对象分配空间并返回一个指向该对象的指针,我们可以选择对对象进行初始化;delete,接受一个动态对象的指针,销毁该对象,并释放与之关联的内存。
  • 新标准库提供了两种智能指针(smart pointer)类型来管理动态对象:shared_ptr允许多个指针指向同一个对象;unique_ptr则“独占”所指向的对象。标准库还定义了一个名为weak_ptr的伴随类,它是一种弱引用,指向shared_ptr所管理的对象。

shared_ptr

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

  • shared_ptrunique_ptr都支持的操作

  • shared_ptr独有的操作

  • 一旦一个shared_ptr的计数器变为0,它将自动释放自己所管理的对象:

    1
    2
    3
    4
    5
    auto r = make_shared<int>(42); // int to which r points has one user
    r = q; // assign to r, making it point to a different address
    // increase the use count for the object to which q points
    // reduce the use count of the object to which r had pointed
    // the object r had pointed to has no users; that object is automatically freed

直接管理内存

  • 运算符new分配内存,delete释放new分配的内存。
  • 默认情况下,动态分配的对象是默认初始化的:

    1
    2
    string *ps = new string; // initialized to empty string
    int *pi = new int; // pi points to an uninitialized int
  • 可以使用直接初始化的方式初始化一个动态分配的对象,可以使用传统的构造方式,也可以使用列表初始化:

    1
    2
    3
    4
    int *pi = new int(1024); // object to which pi points has value 1024
    string *ps = new string(10, '9'); // *ps is "9999999999"
    // vector with ten elements with values from 0 to 9
    vector<int> *pv = new vector<int>{0,1,2,3,4,5,6,7,8,9};
  • 由于编译器使用初始值类型来推断需要分配内存的类型,只有当括号中仅有单一初始化器时才可以使用auto

    1
    2
    3
    auto p1 = new auto(obj); // p points to an object of the type of obj
    // that object is initialized from obj
    auto p2 = new auto{a,b,c}; // error: must use parentheses for the initializer
  • 类似其他const对象,动态分配的const对象必须进行初始化。

  • new不能分配所要求的内存空间,它会抛出一个类型为bad_alloc的异常,可以改变new的使用方式阻止其抛出异常:
    1
    2
    3
    // if allocation fails, new returns a null pointer
    int *p1 = new int; // if allocation fails, new throws std::bad_alloc
    int *p2 = new (nothrow) int; // if allocation fails, new returns a null pointer

shared_ptrnew结合使用

  • 定义和改变shared_ptr的其他方法

  • 接受指针参数的智能指针构造函数是explicit的:

    1
    2
    shared_ptr<int> p1 = new int(1024); // error: must use direct initialization
    shared_ptr<int> p2(new int(1024)); // ok: uses direct initialization
  • 默认情况下,智能指针使用delete释放与之关联的对象。

  • 当将一个shared_ptr绑定到一个普通指针时,我们就将内存的管理责任交给了这个shared_ptr。一旦这样做了,我们就不应该再使用内置指针来访问shared_ptr所指向的内存了。
  • 虽然编译器不会给出报错信息,但将另一个智能指针也绑定到get返回的指针上是错误的。

智能指针与异常

  • 如果一个异常发生在newdelete之间,并且异常没有在f中捕获,那么内存将永远不会被释放:
    1
    2
    3
    4
    5
    6
    void f()
    {
    int *ip = new int(42); // dynamically allocate a new object
    // code that throws an exception that is not caught inside f
    delete ip; // free the memory before exiting
    }

unique_ptr

  • 不同于shared_ptr,同时只能有一个unique_ptr指向一个给定对象。
  • unique_ptr操作

  • shared_ptr不同,没有类似make_shared的标准库函数返回一个unique_ptr。当我们定义一个unique_ptr时,需要将其绑定到一个new返回的指针上。类似shared_ptr,初始化unique_ptr必须采用直接初始化形式:

    1
    2
    unique_ptr<double> p1; // unique_ptr that can point at a double
    unique_ptr<int> p2(new int(42)); // p2 points to int with value 42
  • 不能拷贝unique_ptr的规则有一个例外:我们拷贝或赋值一个将要被销毁的unique_ptr,最常见的例子是从函数返回一个unique_ptr

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    unique_ptr<int> clone(int p) {
    // ok: explicitly create a unique_ptr<int> from int*
    return unique_ptr<int>(new int(p));
    }

    unique_ptr<int> clone(int p) {
    unique_ptr<int> ret(new int (p));
    // . . .
    return ret;
    }

weak_ptr

动态数组

  • 分配了动态数组的类必须定义自己版本的操作,在拷贝、复制及销毁对象时管理所关联的内存。

new和数组

  • 使用new分配一个数组时,获得的不是数组类型的对象,而是指向数组元素类型的指针。由于得到的不是数组类型,不能对动态数组调用beginend
  • 默认情况下,new分配的对象,不管是单个分配还是数组中的,都是默认初始化的。可以对数组中的元素进行值初始化,方法是在大小之后跟一队空括号。在新标准中,还可以提供一个元素初始化器的花括号列表:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    int *pia = new int[10]; // block of ten uninitialized ints
    int *pia2 = new int[10](); // block of ten ints value initialized to 0
    string *psa = new string[10]; // block of ten empty strings
    string *psa2 = new string[10](); // block of ten empty strings

    // block of ten ints each initialized from the corresponding initializer
    int *pia3 = new int[10]{0,1,2,3,4,5,6,7,8,9};
    // block of ten strings; the first four are initialized from the given initializers
    // remaining elements are value initialized
    string *psa3 = new string[10]{"a", "an", "the", string(3,'x')};
  • 为了释放动态数组,必须使用一种特殊形式的delete,在指针前加上一个空方括号对:

    1
    2
    delete p; // p must point to a dynamically allocated object or be null
    delete [] pa; // pa must point to a dynamically allocated array or be null
  • 数组中的元素按逆序销毁。

  • 指向数组的unique_ptr

  • unique_ptr不同,shared_ptr不支持直接管理动态数组,如果希望使用shared_ptr管理一个动态数组,必须提供自己定义的删除器:

    1
    2
    3
    // to use a shared_ptr we must supply a deleter
    shared_ptr<int> sp(new int[10], [](int *p) { delete[] p; });
    sp.reset(); // uses the lambda we supplied that uses delete[] to free the array

allocator

  • 不能分配元素类型为“没有默认构造函数的类类型”的动态数组。
  • 标准库allocator类及其算法

  • allocator算法


本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!