动态内存Notes

shared_ptr && unique_ptr && weak_ptr

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

除了静态内存和栈内存,每个程序还拥有一个内存池。这部分内存叫做自由空间或堆。程序用对来存储动态分配的对象。

C++ 11 提供两种智能指针来管理动态对象:

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

shared_ptr

操作描述
shared_ptr<T> p, unique_ptr<T> p空智能指针,可以指向类型为T的对象
swap(p, q)交换 p 和 q 的指针
make_shared(args)返回一个 shared_ptr,指向一个动态分配的 T对象,并使用 args 初始化
shared_ptr p(q)p 是 shared_ptr q 的拷贝;此操作会递增 q 的计数器,q 中的指针必须能转换为 T*
p = qp 和 q 都是 shared_ptr, 所保存的指针必须能相互转换。此操作会递减 p 的引用计数器,会递增 q 的引用计数器;若 p 的引用计数器变为 0,则会将其管理的内存释放。
p.unique()若 p.use_count() 为 1, 则返回 true,or 返回 false
p.use_count()返回与 p 共享对象的智能指针数量;可能很慢,主要用于调试。
make_shared 函数

最安全的分配和使用动态内存的方法是使用 make_shared<T>(args) 函数

类似shared_ptr<int> ptr = make_shared<T>(421);

args 的参数必须符合 T 的构造函数参数顺序。(找到可以这样调用的构造函数)

shared_ptr 拷贝,赋值

每当进行 shared_ptr 的拷贝和赋值的时候,每个 shared_ptr 都会记录有多少个其他的 shared_ptr 指向相同的对象。

我们可以认为每个 shared_ptr 都有一个关联的计数器,称为引用计数

shared_ptr 自动销毁所管理的对象

当指向对象的最后一个 shared_ptr 被销毁时, shared_ptr 会调用类的析构函数完成销毁工作。shared_ptr 的析构函数会递减它所指对象的引用计数。如果引用计数为 0 ,shared_ptr 就会销毁指向对象,并且释放内存空间。

Tips:

如果将 shared_ptr 存于容器中,然后之后对容器进行容器重排等之类的操作,使得你不再需要某些元素,要使用 erase 函数删除不需要的元素。

new 操作

new 操作如果内存耗尽,则会抛出 bad_alloc 异常,程序会终止运行。

我们可以 int* ptr = new (nothrow) int; 来防止抛出异常,这样如果内存耗尽, ptr 就为一个空指针。

delete

delete 操作对空指针总是正确的。

动态对象直到被释放前都会一直存在,占用内存空间。

使用 new 和 delete 常见问题:

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

delete 释放内存之后,指针还是存在的,依旧保存着那个内存地址,这样的指针就变成了悬空指针

可以在进行一次空指针赋值来进行一定的保护,但依旧推荐使用智能指针

int *p = new int(5);
delete p;
p = nullptr;
shared_ptr && new

我们可以使用 new 返回的指针来初始化 shared_ptr。接受指针参数的智能指针构造函数是 explicit的。因此我们不能将一个内置指针隐式转换为一个智能指针,必须使用直接初始化,且函数返回也必须如此。

shared_ptr<int> p(new int(42));
shared_ptr<int> func(int p) {
  return shared_ptr<int> (new int(p));
}
操作描述
shared_ptr p(q)p 管理内置指针 q 管理的对象,q 必须指向 new 分配的内存,且能够转换为 T*
shared_ptr p(u)p 从 unique_ptr u 接管对象所有权,将 u 置空
shared_ptr p(q, d)p 接管内置指针 q, 并且调用 d 代替 delete
p.reset() && p.reset(q) && p.reset(q, d)若 p 是唯一的,则会释放对象,若传递了内置指针 q,会让 p 指向 q,否则置空 p。若还传递参数 d,则会调用 d 来释放 q 代替 delete。
p.get()返回一个内置指针,(保证在代码内不会出现释放内存才使用这个函数)
指针和异常

当程序发生异常的时候,如果使用的是智能指针,因发生异常是会跳出代码段的,智能指针就会检测引用计数,释放内存。但是如果使用的是内置指针,在异常处理时没有进行相应的释放,则会保留内存空间。

智能指针和 哑类

很多C++的类并没有很好的定义析构函数,而且对于网络库,一般并不是直接释放内存,而是使用函数关闭连接,如果继续使用默认的 delete 就有可能会出错。故我们需要自己定义释放操作,并且利用 shared_ptr 的检测达到想要的功能。

void end_connection(connection *p) { disconnect(*p); }
void f(destination &d) {
  connection c = connect(&d);
  shared_ptr<connection> p(&c, end_connection);
  // 使用连接
  // 当 f 退出(即使是因为异常退出),connection 都会正常关闭,即通过 shared_ptr 调用 end_connection();
}

Tips:

  • 不使用相同的内置指针初始化多个智能指针
  • 不 delete get() 返回的指针
  • 不使用 get() 初始化 或 reset() 另一个智能指针
  • 如果使用 get() 返回的指针,如果最后一个智能指针被销毁,则该指针无效
  • 如果使用的智能指针不是 new 分配的内存,记住传递一个删除器

unique_ptr

当我们定义一个 unique_ptr 时,需要将其绑定到一个 new 返回的指针上,且必须是直接初始化的形式。

操作描述
unique_ptr u1空的智能指针,销毁时调用 delete
unique_ptr u1会使用一个类型为 D 的可调用对象来释放指针
unique_ptr u1(d)会使用一个类型为 D 的可调用对象 d 来释放指针,d 类似于删除器
u = nullptr释放 u 指向的对象,置空 u
u.reset(), u.reset(p), u.reset(nullptr)类似 shared_ptr
u.release()放弃对指针的控制,返回指针,并且置空 u

转移控制权操作 u1.reset(u2.release()); , 如果只单纯的调用 u1.release() 则我们会丢失指针,导致内存不能释放。

unique_ptr 传递参数 和 返回值

unique_ptr 是不能拷贝和赋值的。但是有一个例外:我们可以拷贝或赋值一个将要被销毁的 unique_ptr。

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

这里编辑执行一种特殊的拷贝。

向 unique_ptr 传递删除器

unique_ptr<T, delctype(deleter)*> u(deleter);

weak_ptr

weak_ptr 是一种不控制所指对象生命周期的智能指针。将一个 weak_ptr 绑定到一个 shared_ptr 不会改变其引用计数。也就是说即使有 weak_ptr 指向的对象, shared_ptr 该要销毁的依旧销毁。

操作描述
weak_ptr w定义空的 weak_ptr
weak_ptr w(sp)直接初始化 shared_ptr 参数
w = pp 可以是一个 shared_ptr 或 weak_ptr
w.reset()置空
w.use_count()与 w 共享对象的 shared_ptr 的数量
w.expired()若 use-count 为 0 返回 true
w.lock()若 expired 返回 true, 则返回一空的 shared_ptr,否则返回指向对象的 shared_ptr

动态数组

type *p = new type[array_size]; delete [] p;

由于分配内存并不是一个数组类型,故不能对动态数组使用 begin, end获得,同样也不能使用 范围 for 来遍历动态数组。动态数组不是数组类型

分配一个空的动态数组是合法的。

智能指针和动态数组

unique_ptr 指针是可以管理动态数组的,unique_ptr 支持下标访问。

unique_ptr<int[]> p(new int[10]); p.release();

shared_ptr 可以通过添加删除器(deleter)管理动态数组,但是不支持下表访问,访问动态数组元素比较麻烦

shared_ptr<int> sp(new int[10], [](int *p) {delete[] p;});
sp.reset();
for(size_t i = 0; i != 10; ++i) {
  *(sp.get() + i) = i;
  //通过 get 获得内置指针然后再访问动态数组元素。
}

allocator 类

allocator 帮助我们将内存分配和对象构造分开来。它提供一种类型感知的内存分配,它分配的内存是原始的,未构造的。

操作描述
allocator a定义一个allocator对象。
a.allocate(n)分配一段原始的、未构造的内存,保存 n 个对象
a.deallocate(p,n)释放内存,p 必须是 allocate 返回的指针,而且 n 必须是 allocate 的参数,都要一致。该函数会对每一个内存对象调用 destroy
a.construct(p, args)p必须是 T*类型指针,指向一块原始内存;args被传递给类型为 T 的构造函数,用来在 p 指向的内存中构造一个对象
a.destroy(p)p 为 T*, 此算法对 p 所指向的对象调用析构函数。不会释放内存

使用 allocate 返回的内存,解引操作之类,必须先构造对象,调用 construct 函数。否则该行为是未定义的,

allocator 还提供几个伴随算法:

  • uninitialized_copy(beg, end, beg2) : 从迭代器 [beg, end) 的范围复制对象到beg2指向的未构造的原始内存中
  • uninitialized_copy_n(beg, n, beg2):类似。指定数量。
  • uninitialized_fill(beg, end, T t):在迭代器[beg, end) 指定的原始内存中构造对象为 t 的对象
  • uninitialized_fill_n(beg, n, T t) : 类似,指定数量。
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值