第04章 C++语言专题(一.05)动态内存

声明:仅为个人学习总结,还请批判性查看,如有不同观点,欢迎交流。

摘要

本文主要包括:通过 new/delete、shared_ptr、unique_ptr 进行单个对象的动态创建和销毁,通过 new/delete、allocator 进行对象数组的动态分配和释放,以及通过智能指针进行动态数组的管理。


对象及其生存期

对象类型责任方生存期存储区域
全局对象编译器在程序启动时创建,在程序结束时销毁静态内存
局部自动对象编译器在进入其定义所在的程序块时被创建,在离开块时销毁栈内存
局部 static 对象编译器在第一次使用前创建,在程序结束时销毁静态内存
动态分配对象程序在需要时显式创建,在不需要时显式(或由智能指针自动)销毁堆内存/自由空间

1、动态内存与智能指针

程序使用动态内存的原因:

  • 不知道需要使用多少对象;
  • 不知道所需对象的准确类型;
  • 需要在多个对象间共享数据。

1.1 智能指针(C++11)

智能指针(smart pointer),行为类似常规指针,但是能够自动释放所指向的对象。

  • 智能指针是模板,创建智能指针时,必须提供指针可以指向的类型;
  • 默认初始化的智能指针中保存着一个空指针;
  • 智能指针的使用方式与普通指针类似:
    • 解引用一个智能指针,返回它所指向的对象;
    • 在条件判断中使用智能指针,效果就是检测它是否为空。

C++11 标准库提供了两种智能指针类型:

  • shared_ptr,允许多个指针指向同一个对象;
    • weak_ptr,是一种弱引用,指向 shared_ptr 所管理的对象。
  • unique_ptr,“独占”所指向的对象。

智能指针操作:

序号操作说明shared_ptrunique_ptr
1shared_ptr<T> sp
unique_ptr<T> up
定义空智能指针,可以指向类型为 T 的对象
2pp 用作一个条件判断,若 p 指向一个对象,则为 true
3*p解引用 p,获得它指向的对象
4p->mem等价于 (*p).mem
5p.get()返回 p 中保存的指针。谨慎使用,如果智能指针释放了其对象,那么返回的指针所指向的对象也就消失了
6swap(p, q)
p.swap(q)
交换 pq 中的指针
1make_shared<T>(args)返回一个 shared_ptr,指向一个动态分配的类型为 T 的对象。使用 args 初始化该对象
2shared_ptr<T> p(q)pshared_ptr q 的拷贝;此操作会递增 q 的引用计数。q 中的指针必须能转换为 T*
3p = qpp 都是 shared_ptr,所保存的指针必须能相互转换。
此操作会递增 q 的引用计数,递减 p 的引用计数;如果 p 的引用计数变为 0,则释放其管理的原内存
4p.use_count()返回与 p 共享对象的智能指针数量;可能很慢,主要用于调试
5p.unique()如果 p.use_count() 为 1,返回 true,否则返回 false
6shared_ptr<T> p(q)p 管理内置指针 q 指向的对象;q 必须指向 new 分配的内存,且能够转换为 T* 类型
7shared_ptr<T> p(u)punique_ptr u 那里接管了对象的所有权;将 u 置为空
8shared_ptr<T> p(q,d)p 接管了内置指针 q 所指向的对象的所有权。q 必须能转换成 T* 类型。p 将使用可调用对象 d 来代替 delete
9shared_ptr<T> p(p2,d)pshared_ptr p2 的副本,p 将使用可调用对象 d 来代替 delete
10p.reset()p 是唯一指向其对象的 shared_ptrreset 会释放此对象
11p.reset(q)若传递了可选的内置指针 q,会令 p 指向 q,否则会将 p 置为空
12p.reset(q,d)若还传递了参数 d,将会调用 d 而不是 delete 来释放 q
1unique_ptr<T> u1定义空 unique_ptr,可以指向类型为 T 的对象。u1 会使用 delete 来释放它的指针
2unique_ptr<T, D> u2u2 会使用类型 D 的可调用对象代替 delete
3unique_ptr<T, D> u(d)u 会使用类型 D 的对象 d 代替 delete
4u = nullptr释放 u 指向的对象,将 u 置为空
5u.release()u 放弃对指针的控制权,返回指针,并将 u 置为空
6u.reset()释放 u 指向的对象
7u.reset(q)如果提供了内置指针 q,令 u 指向这个对象
8u.reset(nullptr)否则将 u 置为空

1.2 shared_ptr 类

1.2.1 make_shared 函数

标准库函数 make_shared,在动态内存中分配一个对象并初始化它,返回指向此对象的 shared_ptr

  • make_shared 用其参数来构造给定类型的对象;
  • 如果不传递任何参数,对象就会进行值初始化:
    • 对于内置类型,初始值自动设为 0;
    • 对于类类型,进行默认初始化。
shared_ptr<string> p1;                                // shared_ptr that can point at a string
shared_ptr<list<int>> p2;                             // shared_ptr that can point at a list of ints
shared_ptr<int> p3 = make_shared<int>(42);            // shared_ptr that points to an int with value 42
shared_ptr<string> p4 = make_shared<string>(10, '9'); // p4 points to a string with value 9999999999
shared_ptr<int> p5 = make_shared<int>();              // p5 points to an int that is value initialized to 0
// 通常用 auto 定义一个对象来保存 make_shared 的结果
auto p6 = make_shared<vector<string>>(); // p6 points to a dynamically allocated, empty vector<string>

1.2.2 拷贝、赋值和销毁

可以认为每个 shared_ptr 都有一个关联的计数器(引用计数,reference count)。

  • 拷贝一个 shared_ptr,计数器会递增:
    • 用一个 shared_ptr 初始化另一个 shared_ptr
    • shared_ptr 作为参数传递给一个函数;
    • shared_ptr 作为函数的返回值。
  • 销毁一个 shared_ptr,计数器会递减,变为 0 时,自动释放所管理的对象:
    • 一个局部的 shared_ptr 离开其作用域;
    • 调用 reset 成员。
  • shared_ptr 赋予一个新值:
    • 右侧运算对象,计数器会递增;
    • 左侧运算对象,计数器会递减,变为 0 时,自动释放所管理的对象。
auto p = make_shared<int>(42); // p 指向的对象只有 p 一个引用者
auto q(p);                     // p 和 q 指向相同对象,此对象有两个引用者
auto r = make_shared<int>(42);
r = q;  // 给 r 赋值,令它指向另一个地址
        // 递增 q 指向的对象的引用计数
        // 递减 r 原来指向的对象的引用计数
        // r 原来指向的对象已经没有引用者,会自动释放

1.3 直接管理内存

在 C++ 中,通过一对运算符来直接管理动态内存:

  • new,为对象分配空间,进行初始化(如果需要),返回指向对象的指针;
  • delete,接受指向对象的指针,销毁对象、释放内存。

1.3.1 分配与初始化对象

动态分配对象:

  • 在自由空间分配的内存是未命名的(无名对象);
  • 返回一个指向分配的对象的指针。
int *pi = new int;  // pi 指向一个动态分配的、未初始化的、无名对象

初始化对象:

  • 默认情况下,进行默认初始化:
    • 内置类型或组合类型的对象,值将是未定义的;
    • 类类型的对象,将用默认构造函数进行初始化。
  • 可以进行直接初始化:
    • 使用传统的构造方式(使用圆括号);
    • 使用列表初始化方式(使用花括号,C++11)。
  • 可以进行值初始化(在类型名之后加上一对空的圆括号)。
int *pi = new int(1024);                    // 直接初始化,*pi 的值为 1024
string *ps = new string(10, '9');           // 直接初始化 *ps 为 "9999999999"
vector<int> *pv = new vector<int>{0, 1, 2}; // 直接初始化 vector 有 3 个元素,值从 0 到 2

string *ps1 = new string;   // 默认初始化,值为空 string
string *ps2 = new string(); // 值初始化,值为空 string

int *pi1 = new int;   // 默认初始化,*pi1 的值未定义
int *pi2 = new int(); // 值初始化,*pi2 的值为 0

可以使用 auto 从初始化器推断想要分配的对象的类型:

  • 初始化器被圆括号包围;
  • 在括号中仅有单一初始化器。
auto p1 = new auto(obj);     // p1 指向一个与 obj 类型相同的对象,该对象用 obj 进行初始化
auto p2 = new auto(a, b, c); // 错误:括号中只能有单个初始化器

可以用 new 分配 const 对象:

  • 必须进行初始化:
    • 定义了默认构造函数的类类型,可以隐式初始化;
    • 其他类型的对象,必须显示初始化。
  • 返回一个指向 const 的指针。
const int *pci = new const int(1024); // 分配并初始化一个 const int
const string *pcs = new const string; // 分配并默认初始化一个 const 的空 string

1.3.2 内存耗尽

一旦一个程序用光了它所有可用的内存,new 表达式就会失败:

  • 默认情况下,它会抛出一个类型为 bad_alloc 的异常;
  • 可以传递 nothrownew,告诉 new 不能抛出异常:
    • 定位 new (placement new) 表达式,允许向 new 传递额外的实参;
    • nothrow 是由标准库定义的对象。
int *p1 = new int;           // 如果分配失败,new 抛出 std::bad_alloc
int *p2 = new (nothrow) int; // 如果分配失败,new 返回空指针

1.3.3 释放动态内存

由内置指针管理的动态对象,需要进行显式释放,才会被销毁。
通过 delete 表达式,销毁给定的指针指向的对象,释放对应的内存。

delete p;    // p 必须指向一个动态分配的对象,或是一个空指针
p = nullptr; // p 不再绑定到任何对象

const int *pci = new const int(1024);
delete pci; // 正确:释放一个 const 对象

使用 newdelete 管理动态内存的 3 个常见问题:

  1. 忘记 delete 内存,导致“内存泄漏”;
  2. 使用已经 delete 的对象;
  3. 同一块内存被重复 delete

1.4 shared_ptr 和 new 结合

可以用 new 返回的指针来初始化智能指针,接受指针参数的智能指针构造函数是 explicit 的。

shared_ptr<int> p1(new int(42));  // 正确:必须使用直接初始化形式
shared_ptr<int> p2 = new int(42); // 错误:不能将内置指针隐式地转换为智能指针

shared_ptr<string> p;
// 可以用 reset 将一个新的指针赋值给 shared_ptr
// 与赋值类似,reset 会更新引用计数,如果需要的话,会释放 p 指向的对象
// p = new string;   // 错误:不能将一个指针赋予 shared_ptr
p.reset(new string); // 正确:p 指向一个新对象

// reset 成员经常与 unique 一起使用,来控制多个 shared_ptr 之间共享的对象
if (!p.unique())
  p.reset(new string(*p)); // 不是唯一用户,分配新的拷贝
*p += newVal;              // 已经是唯一的用户,可以改变对象的值

1.4.1 不要混合使用普通指针和智能指针

shared_ptr 可以协调对象的析构,但仅限于其自身的拷贝(也是 shared_ptr)之间。

  • 尽量使用 make_shared 而不是 new,能够在分配对象的同时就将 shared_ptr 与之绑定,避免无意中将同一块内存绑定到多个独立创建的 shared_ptr 上。
  • 在将一个 shared_ptr 绑定到普通指针上之后,就不应该再使用内置指针来访问 shared_ptr 所指向的内存,因为无法知道对象何时会被销毁。
// ptr is created and initialized when process is called
void process(shared_ptr<int> ptr)
{
  // use ptr
} // ptr goes out of scope and is destroyed

int main()
{
  shared_ptr<int> p(new int(42)); // reference count is 1
  process(p);  // copying p increments its count; in process the reference count is 2
  int i = *p;  // ok: reference count is 1

  int *x(new int(1024));       // dangerous: x is a plain pointer, not a smart pointer
  process(x);                  // error: cannot convert int* to shared_ptr<int>
  process(shared_ptr<int>(x)); // legal, but the memory will be deleted!
  int j = *x;                  // undefined: x is a dangling pointer!

  return 0;
}

1.4.2 不要使用 get 初始化另一个智能指针

智能指针的 get 函数,可以返回一个内置指针,指向智能指针管理的对象:

  • 用于将内置指针传递给无法使用智能指针的代码;
  • 使用 get 返回的指针的代码不能 delete 此指针。
    • 不要用 get 初始化另一个智能指针或者为另一个智能指针赋值。
shared_ptr<int> p(new int(42)); // reference count is 1
int *q = p.get();  // ok: but don't use q in any way that might delete its pointer
{ // new block
  // undefined: two independent shared_ptrs point to the same memory
  shared_ptr<int>(q);
} // block ends, q is destroyed, and the memory to which q points is freed
int foo = *p; // undefined; the memory to which p points was freed

1.5 智能指针和异常

使用异常处理的程序,需要确保在异常发生后资源能被正确地释放。一个简单的确保资源被释放的方法是使用智能指针。

// 使用智能指针管理内存,即使程序块过早结束,智能指针类也能确保在内存不再需要时将其释放
void f1()
{
  shared_ptr<int> sp(new int(42)); // allocate a new object
  // code that throws an exception that is not caught inside f1
} // shared_ptr freed automatically when the function ends

// 使用内置指针管理内存,如果在 new 之后 delete 之前发生了异常,内存不会被释放
void f2()
{
  int *ip = new int(42); // dynamically allocate a new object
  // code that throws an exception that is not caught inside f2
  delete ip; // free the memory before exiting
}

对于不能通过 delete 释放的资源,可以使用自定义的释放操作。

// 假设正在使用 C 和 C++ 都使用的网络库(没有析构函数),使用该库的程序可能包含以下代码:

struct destination;                // represents what we are connecting to
struct connection;                 // information needed to use the connection
connection connect(destination *); // open the connection
void disconnect(connection);       // close the given connection

// 为了使用 shared_ptr 来管理 connection,必须首先定义一个函数来代替 delete
// 这个删除器 (deleter) 函数必须能够对 shared_ptr 中保存的指针进行释放
void end_connection(connection *p) { disconnect(*p); }

void f(destination &d /* other parameters */)
{
  connection c = connect(&d);
  shared_ptr<connection> p(&c, end_connection);
  // use the connection
  // when f exits, even if by an exception, the connection will be properly closed
}

1.6 unique_ptr

一个 unique_ptr “独占”它所指向的对象,某个时刻只能有一个 unique_ptr 指向一个给定对象。当 unique_ptr 被销毁时,它所指向的对象也被销毁。

  • 定义一个 unique_ptr 时,需要将其绑定到一个 new 返回的指针上;
    • shared_ptr 不同,没有类似 make_shared 的标准库函数返回一个 unique_ptr
  • shared_ptr 一样,初始化 unique_ptr 必须采用直接初始化形式;
  • unique_ptr 不支持拷贝构造函数、拷贝赋值运算符(因为“独占”);
  • unique_ptr 支持移动构造函数、移动赋值运算符;
  • 可以通过调用 releasereset 将指针的所有权从一个(非constunique_ptr 转移给另一个 unique_ptr
// unique_ptr 必须采用直接初始化形式
unique_ptr<double> p1;           // 可以指向一个 double 的 unique_ptr
unique_ptr<int> p2(new int(42)); // p2 指向一个值为 42 的 int

// 由于一个 unique_ptr 独占它指向的对象,因此 unique_ptr 不支持普通的拷贝或赋值操作
unique_ptr<string> p1(new string("Stegosaurus"));
unique_ptr<string> p2(p1); // error: no copy for unique_ptr
unique_ptr<string> p3;
p3 = p2; // error: no assign for unique_ptr

// transfers ownership from p1 (which points to the string Stegosaurus) to p2
unique_ptr<string> p2(p1.release());       // release makes p1 null
unique_ptr<string> p3(new string("Trex")); // transfers ownership from p3 to p2
p2.reset(p3.release());  // reset deletes the memory to which p2 had pointed

// 调用 release 会切断 unique_ptr 和它原来管理的对象之间的联系
// 如果不用另一个智能指针来保存 release 返回的指针,程序就要负责资源的释放
p2.release();          // 错误:p2 不会释放内存,并且丢失了指针
auto p = p2.release(); // 正确:但必须记得 delete(p)

// 可以拷贝或赋值一个将要被销毁的 unique_ptr,例如传递 unique_ptr 参数和返回 unique_ptr
// 在此情况下,编译器知道对象将要被销毁,将执行移动操作
unique_ptr<int> clone(int p) {
  // ok: explicitly create a unique_ptr<int> from int*
  unique_ptr<int> ret(new int(p));
  // . . .
  return ret;
}

对于不能通过 delete 释放的资源,可以使用自定义的释放操作。
shared_ptr 不同的是,重载一个 unique_ptr 中的删除器会影响到 unique_ptr 类型以及如何构造(或 reset)该类型的对象。

// p 指向一个类型为 objT 的对象,并使用一个类型为 delT 的对象释放 objT 对象
// 它会调用一个名为 fcn 的 delT 类型的对象(删除器)
unique_ptr<objT, delT> p(new objT, fcn);

// 使用 unique_ptr 重写连接程序:
void f(destination &d /* other needed parameters */)
{
  connection c = connect(&d); // open the connection
  // when p is destroyed, the connection will be closed
  unique_ptr<connection, decltype(end_connection) *> p(&c, end_connection);
  // use the connection
  // when f exits, even if by an exception, the connection will be properly closed
}

1.7 智能指针使用规范

智能指针可以提供对动态分配的内存安全而又方便的管理,但这建立在正确使用的前提下。为了正确使用智能指针,必须坚持一些基本规范。

智能指针使用规范:

  • 不使用相同的内置指针初始化(或 reset)多个智能指针;
  • delete get() 返回的指针;
  • 不使用 get() 初始化或 reset 另一个智能指针;
  • 如果使用 get() 返回的指针,记住当最后一个对应的智能指针销毁后,返回的指针就变为无效了;
  • 如果使用智能指针管理的资源不是 new 分配的内存,需要传递给它一个删除器。

1.8 weak_ptr

weak_ptr 也是一种智能指针,但它不控制所指向对象的生存期,它指向一个由 shared_ptr 管理的对象。

  • 将一个 weak_ptr 绑定到一个 shared_ptr 不会改变 shared_ptr 的引用计数(是“弱”共享);
  • 通过调用 weak_ptrlock 成员访问其对象,可以防止用户访问一个不再存在的对象。
序号操作说明
1weak_ptr<T> w定义空 weak_ptr,指向类型为 T 的对象
2weak_ptr<T> w(sp)指向与 shared_ptr sp 相同的对象,T 必须能够转换为 sp 指向的类型
3w = pp 可以是一个 shared_ptrweak_ptr,赋值后 wp 共享对象
4w.reset()w 置为空
5w.use_count()w 共享对象的 shared_ptr 的数量
6w.expired()如果 w.use_count() 为 0,返回 true,否则返回 false
7w.lock()如果 expiredtrue,返回一个空 shared_ptr;否则返回一个指向 w 的对象的 shared_ptr
// 当创建一个 weak_ptr 时,要用一个 shared_ptr 来初始化它
auto p = make_shared<int>(42);
weak_ptr<int> wp(p); // wp weakly shares with p; use count in p is unchanged

// 由于对象可能不存在,所以不能使用 weak_ptr 直接访问对象,而必须调用 lock
// 如果对象存在,lock 返回一个指向共享对象的 shared_ptr
if (shared_ptr<int> np = wp.lock())
{ // true if np is not null
  // inside the if, np shares its object with p
}

2、动态数组

newdelete 运算符一次分配/释放一个对象,有时需要一次分配/释放多个对象。

  • C++ 语言定义了另一种 new 表达式语法,可以分配并初始化一个对象数组;
  • 标准库中的 allocator 类,允许将分配和初始化分离(通常性能更好管理更灵活)。

2.1 new 和数组

2.1.1 分配动态数组

new 分配一个对象数组:在类型名之后跟一对方括号,在其中指明要分配的对象的数目(整型,不必是常量),返回指向第一个对象的指针(而不是一个数组类型的对象)。

// call get_size to determine how many ints to allocate
int *pia = new int[get_size()]; // pia points to the first of these ints

typedef int arrT[42]; // arrT names the type array of 42 ints
int *p = new arrT;    // allocates an array of 42 ints; p points to the first one

虽然不能创建大小为 0 的数组变量,但当 n 为 0 时,调用 new[n] 是合法的。
对于零长度的数组来说,new 返回一个合法的非空指针,不能解引用,但可以像使用尾后迭代器一样使用这个指针(进行比较操作,加上/减去 0,减去自身得到 0)。

char arr[0];            // error: cannot define a zero-length array
char *cp = new char[0]; // ok: but cp can't be dereferenced

size_t n = get_size(); // get_size returns the number of elements needed
int *p = new int[n];   // allocate an array to hold the elements
// 当 n 为 0 时,循环中的条件会失败(q 等于 p+n),循环体不会被执行
for (int *q = p; q != p + n; ++q)
  /* process the array */;

2.1.2 初始化数组

在分配对象数组的同时,可以进行对象的初始化。

  • 默认情况下,进行默认初始化:
    • 内置类型或组合类型的对象,值将是未定义的;
    • 类类型的对象,将用默认构造函数进行初始化。
  • 可以进行值初始化(在大小之后加上一对空的圆括号):
    • 不能在括号中给出初始化器,也就不能使用 auto 来分配数组。
  • 可以提供一个元素初始化器的花括号列表(C++11,同内置数组对象):
    • 如果初始化器数目小于元素数目,剩余元素将进行值初始化;
    • 如果初始化器数目大于元素数目, new 表达式失败。
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')};

2.1.3 释放动态数组

释放动态数组:使用 delete [] 指针;

  • 指针指向一个对象数组的第一个元素;
  • 销毁数组中的元素,并释放对应的内存;
  • 元素按逆序销毁,即,先销毁最后一个元素。
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

2.1.4 智能指针和动态数组

2.1.4.1 unique_ptr 和动态数组

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

// 定义一个管理动态数组 unique_ptr 时,必须在对象类型后面跟一对空方括号
// 当 unique_ptr 销毁它所管理的指针时,自动使用 delete[]
unique_ptr<int[]> up(new int[10]); // up points to an array of ten uninitialized ints
up.reset();                        // automatically uses delete[] to destroy its pointer

当用 unique_ptr 指向数组时,不能使用成员访问运算符 .->,其他操作不变,并新增部分特有操作:

序号操作说明
1unique_ptr<T[]> uu 可以指向一个动态分配的数组,其元素类型为 T
2unique_ptr<T[]> u(p)u 指向内置指针 p 所指向的动态分配的数组,p 必须能够转换为类型 T*
3u[i]使用下标运算符返回 u 拥有的数组中位置 i 处的对象。u 必须指向一个数组

2.1.4.2 shared_ptr 和动态数组

shared_ptr 不直接支持管理动态数组。

  1. 如果希望使用 shared_ptr 管理一个动态数组,必须提供自己定义的删除器。
// 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
  1. 为了访问数组中的元素,必须用 get 获取一个内置指针,然后用它来访问数组元素。
// shared_ptr 未定义下标运算符,而且智能指针类型不支持指针算术运算
for (size_t i = 0; i != 10; ++i)
  *(sp.get() + i) = i; // use get to get a built-in pointer

2.2 allocator 类

使用 new 分配动态数组,有一些灵活性上的局限:

  • 将内存分配和对象构造组合在了一起,不能按需构造对象(数量、数据);
  • 元素可能被赋值两次:第一次是在初始化时,第二次是在赋值时;
  • 没有默认构造函数的类不能动态分配数组。

标准库 allocator 类,将内存分配和对象构造分离开来。它提供一种类型感知的内存分配方法,它分配的内存是原始的、未构造的。

allocator 支持的操作:

序号操作说明
1allocator<T> a定义了一个名为 aallocator 对象,它可以为类型为 T 的对象分配内存。
2a.allocate(n)分配一段原始的、未构造的内存,保存 n 个类型为 T 的对象。
3a.deallocate(p, n)释放从 T* 指针 p 中地址开始的内存,这块内存保存了 n 个类型为 T 的对象;
p 必须是先前由 allocate 返回的指针,n 必须是 p 创建时所要求的大小。
在调用 deallocate 之前,用户必须对每个在这块内存中创建的对象调用 destroy
4a.construct(p, args)pT* 类型的指针,指向一块原始内存;
args 被传递给类型为 T 的构造函数,用来在 p 指向的内存中构造一个对象。
5a.destroy(p)pT* 类型的指针,对 p 指向的对象执行析构函数。

2.2.1 分配内存

allocator 是一个模板。为了定义一个 allocator 对象,必须指明这个 allocator 可以分配的对象类型。
当一个 allocator 对象分配内存时,它会根据给定的对象类型来确定恰当的内存大小和对齐位置。

// 这个 allocate 调用为 n 个 string 分配了内存
allocator<string> alloc;          // object that can allocate strings
auto const p = alloc.allocate(n); // allocate n unconstructed strings

2.2.2 构造对象

allocator 分配的内存是未构造的(unconstructed):

  • 为了使用 allocator 返回的内存,必须用 construct 构造对象;
  • 在用完对象之后,必须对每个构造的对象调用 destroy 来销毁它们。
// 在 C++11 标准库中,construct 成员函数接受一个指针和零个或多个额外参数,
// 指针标识在给定的位置构造一个元素
// 额外参数用来初始化构造的对象,这些参数必须是与构造的对象的类型相匹配的合法的初始化器

auto q = p;                    // q 指向最后构造的元素之后的位置
alloc.construct(q++);          // *q is the empty string
alloc.construct(q++, 10, 'c'); // *q is cccccccccc
alloc.construct(q++, "hi");    // *q is hi

// 还未构造对象的情况下就使用原始内存是错误的
cout << *p << endl; // 正确:使用 string 的输出运算符
cout << *q << endl; // 灾难:q 指向未构造的内存

// 在用完对象之后,必须对每个构造的元素调用 destroy 来销毁它们
// 即,对指向的对象执行析构函数
while (q != p)
  alloc.destroy(--q); // 释放真正构造过的 string

// 一旦元素被销毁后,就可以重新使用这部分内存来保存其他 string,
// 也可以释放 allocator 对象分配的内存
alloc.deallocate(p, n);

2.2.3 拷贝和填充未初始化内存

标准库为 allocator 类定义了两个伴随算法,可以在未初始化的内存中创建对象。

序号操作说明
1uninitialized_copy(b, e, b2)从迭代器 be 指出的输入范围中拷贝元素,到迭代器 b2 指定的未构造的原始内存中。
b2 指向的内存必须足够大,能够容纳输入序列中元素的拷贝。
2uninitialized_copy_n(b, n, b2)从迭代器 b 指向的元素开始,拷贝 n 个元素到 b2 开始的内存中。
3uninitialized_fill(b, e, t)在迭代器 be 指定的原始内存范围中创建对象,对象的值均为 t 的拷贝。
4uninitialized_fill_n(b, n, t)从迭代器 b 指向的内存地址开始,创建 n 个对象,对象的值均为 t 的拷贝。
b 必须指向足够大的未构造的原始内存,能够容纳给定数量的对象。
allocator<int> alloc;
vector<int> vi{0, 1, 2, 3, 4};
// allocate twice as many elements as vi holds
auto p = alloc.allocate(vi.size() * 2);
// construct elements starting at p as copies of elements in vi
// 返回(递增后的)目的位置迭代器 q,即指向最后一个构造的元素之后的位置的指针
auto q = uninitialized_copy(vi.begin(), vi.end(), p);
// initialize the remaining elements to 42
uninitialized_fill_n(q, vi.size(), 42);

参考

  1. [美] Stanley B.Lippman著.C++ Primer 中文版(第5版).电子工业出版社.2013.
  2. [美] Stephen Prata著.C++ Primer Plus(第6版)中文版.人民邮电出版社.2012.

宁静以致远,感谢 Vico 老师。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值