《C++ Primer》读书笔记 第十二章 动态内存

第十二章 动态内存

12.1 动态内存与智能指针

<memory>shared_ptrunique_ptrweak_ptr

12.1.1 shared_ptr 类

1
2
shared_ptr<int> p3 = make_shared<int>(42);
shared_ptr<int> p4 = new int(1024); // 错误,explicit。

递增引用计数:

  • 使用它初始化另一个 shared_ptr。
  • 作为参数传递给一个参数。
  • 作为函数的返回值。

递减引用计数:

  • 赋予一个新值
  • shared_ptr 被销毁(如离开局部作用域)

如果计数器变为0,它就会自动释放自己所管理的对象。

使用动态内存的目的:

  • 程序不知道自己需要使用多少对象。(容器类)
  • 程序不知道所需对象的准确类型。(15章
  • 程序需要在多个对象间共享数据。

12.1.2 直接管理内存

使用new (nothrow)阻止抛异常。

容易出错的地方:

  • 忘记 delete。
  • 使用已经释放掉的对象。
  • 同一块内存释放两次。

所以应坚持使用智能指针!

12.1.3 shared_ptr 和 new 结合使用

1
2
shared_ptr<int> p4 = new int(1024); // 错误,explicit。
shared_ptr<int> p5(new int(1024)); // 正确


依然推荐用 make_shared,而不是混用。

get 函数的存在是为了去调用只支持内置指针的函数,且函数中不能 delete 此指针。不要用 get 初始化另一个智能指针或为另一个智能指针赋值。

reset 和 unique 一起使用:改变底层对象之前,检查自己是否是对象仅有的用户,若不是,改变前制造一份新的拷贝。

1
2
3
if (!p.unique())
p.reset(new string(*p));
*p += newVal;

12.1.4 智能指针和异常

使用智能指针能保证即便程序抛了异常也能销毁局部对象。

自定义删除器(deleter)来使 C 风格的代码也有析构函数的特性。

1
2
3
4
5
6
7
8
void end_connection(connection *p) { disconnect(*p); }

void f (...){
connection c = connect(...);
shared_ptr<connection> p(&c, end_connection);
// 使用连接
// 当 f 退出(即便出于异常),connection也会被正确关闭。
}

智能指针的使用基本规范:

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

12.1.5 unique_ptr

某个时刻只能有一个 unique_ptr 指向一个给定对象,所以不能拷贝和赋值。

但是可以使用releasereset进行转移:

1
2
3
4
5
6
unique_ptr<string> p1(new string("xxx"));
unique_ptr<string> p2(p1.release());
unique_ptr<string> p3(new string("Tex"));
p2.reset(p3.release());
p2.release(); // 错误:不释放内存却丢失了指针,release 一般用来初始化或赋值给另一个指针。
p2.reset();

不能拷贝 unique_ptr 的规则有个例外:将要被销毁的 unique_ptr,如作为函数返回值。

标准库的较早版本使用了一个名为auto_ptr的类,它具有unique_ptr的部分特性,但不是全部,如不能在容器中保存也不能从函数返回,因此,弃用。

传递删除器的版本:

1
unique_ptr<connection, decltype(end_connection)*> p(&c, end_connection);

12.1.6 weak_ptr

不参与计数的 shared_ptr。不能直接使用它访问对象,而必须调用 lock。

1
2
3
if (shared_ptr<int> np = wp.lock()){
...
}

12.2 动态数组

12.2.1 new 和数组

new[]delete[]

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

1
2
3
4
unique_ptr<int[]> up(new int[10]);
for (size_t i = 0; i != 10; ++i)
up[i] = i;
up.release();

但是 shared_ptr 不能直接管理动态数组,需要删除器。

1
2
3
4
shared_ptr<int> sp(new int[10], [](int *p){ delete[] p; });
for (size_t i = 0; i != 10; ++i)
*(sp.get() + i) = i; // shared_ptr 未定义下标运算符,也不支持算术运算。
sp.reset();

12.2.2 allocator 类

定义在<memory>,帮助我们将内存分配和对象构造分离开来,分配未构造的内存。

1
2
3
4
5
6
7
8
9
allocator<string> alloc;
auto const p = alloc.allocate(n);
auto q = p;
alloc.construct(q++);
alloc.construct(q++, 10, 'c');
alloc.construct(q++, "hi");
while (q != p)
alloc.destroy(--q);
alloc.deallocate(p, n);

这些伴随函数返回(递增后的)目的位置迭代器。

12.3 使用标准库:文本查询程序