C++11 新增的智能指针
不当释放指针带来的问题
指针是 C++ 非常强大的功能,但是在实践中,不正确地使用指针会造成一系列的问题
例如,我们定义了一个类 Dog
1 | class Dog { |
在使用指针的过程中,常见的不当释放指针会造成两种后果: 悬空指针 和 内存泄漏
1 | void foo() { |
1 | void foo() { |
正确地释放指针通常是一件麻烦且乏味的工作,C++11 引入了智能指针来帮助程序员正确地释放指针
shared_ptr
shared_ptr
的原理是,在内部维护了一个对象的引用计数,但引用计数归零,即最后一个指向对象的 shared_ptr
被销毁时,将指向的对象也释放掉
基本使用方法
shared_ptr 使用非常简单
1 | void foo() { |
不当使用方法
1 | void foo() { |
因此,我们应该牢记,使用智能指针时,一个对象被创建的时候就应该被赋值给智能指针,而不应该使用原生指针
使用 make_shared
生成 shared_ptr
在使用 shared_ptr
时,C++ 推荐使用 make_shared
函数来生成 shared_ptr
,更快更安全
1 | shared_ptr<Dog> p = make_shared<Dog>("Tank"); |
推荐这样做的原因是,shared_ptr<Dog> p(new Dog("Gunner"))
包含两个步骤:1. 生成 Dog
; 2. 生成 p
;而 make_shared
把这两部合并成了一步
释放对象
shared_ptr 在这些场景下会释放对象:
1 | void foo() { |
自定义 deleter
默认情况下,shared_ptr
的会使用 delete
运算符作为 deleter,但是我们也可以自定义 deleter,例如使用 lambda 函数:
1 | shared_ptr<Dog> pD4( |
当我们使用 shared_ptr
指向数组时,自定义的 deleter 就非常有用了,否则,如果使用默认的 delete
而非 delete[]
除第一个意外的 Dog 就内存泄漏了
1 | shared_ptr<Dog> pDD(new Dog[3], [](Dog* p) {delete[] p;} ); |
从 shared_ptr
获取原生指针
我们可以使用 shared_ptr.get()
方法获取原生指针
1 | shared_ptr<Dog> p1 = make_shared<Dog>("Gunner"); |
如果一定要使用这种方法,要非常小心,否则会带来问题,例如我们引入一个新的类 DogHouse
1 | class DogHouse { |
weak_ptr
weak_ptr
的使用场景
如果我们按照这种方法使用 shared_ptr
, 那么 pD
和 pD2
都不会被释放,造成内存泄漏
1 | class Dog { |
原因是 pD->m_pFriend = pD2
且 pD2->m_pFriend = pD
,这里构成了循环引用,即使出了 main
函数的作用域后 Gunner 和 Smokey 这两个对象仍然保持着一个对方的引用,于是他们两个都不会被释放
为了解决这个问题,我们需要使用 weak_ptr
,weak_ptr
并不会增加 shared_ptr
的引用计数
1 | class Dog { |
访问 weak_ptr
指向的对象
shared_ptr
代表了一种对象的共享所有权,而 weak_ptr
这不拥有对象的所有权,只是保留了一种访问对象的能力,即 weak_ptr
并不关心对象何时以何种方法被释放
因此,weak_ptr
可能会指向一个已经被删除的对象,所以我们想访问 weak_ptr
指向的对象时,必须使用过 weak_ptr.lock()
方法,例如我们要给 Dog
类添加一个 showFriend
方法
1 | class Dog { |
在这个例子中,我们首先使用 weak_ptr.expired()
方法,检查指向的对象是否已被释放了,如果没被释放,则通过 weak_ptr.lock()
方法,生成一个 shared_ptr
方法对象,这样,保证了我们在访问 m_name
属性时,该对象不会被释放
我们同样也可以对 weak_ptr 调用 use_count 方法
1 | cout << m_pFriend.use_count() << endl; |
unique_ptr
unique_ptr
的使用场景
unique_ptr
代表了对象的独有所有权,是一种轻量级的智能指针
在老式的 C++ 中,我们经常使用这样的写法,处理不当,可能内存泄漏
1 | void test(){ |
在 C++11 中,我们只需把原生指针替换为 unique_ptr 即可高枕无忧
1 | void test() { |
当然,这里我们也可以用 shared_ptr
,但是 unique_ptr
更加轻量级
从 shared_ptr
获取原生指针
类似 shared_ptr.get()
,我们可以调用 unique_ptr.release()
来获取原生指针,要注意的是,调用之后该 unique_ptr
会变成空指针
1 | void test() { |
类似的,如果我们对 weak_ptr
调用 reset()
函数或赋值为 nullptr
, 也会导致指向的对象被删除
unique_ptr
之间转移所有权
由于 unique_ptr
代表了对象的独有所有权,因此只能被移动不能被拷贝
1 | void test() { |
unique_ptr
作为参数和返回值
unique_ptr
作为参数时,出了函数作用域会释放对象,作为返回值时,会制动使用移动语义
1 | void f(unique_ptr<Dog> p) { |