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 = q | p 和 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 = p | p 可以是一个 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) : 类似,指定数量。