智能指针浅析(shared_ptr,unique_ptr与weak_ptr)
智能指针的意义
智能指针是一种封装了底层数据指针的高级指针类型。与原始指针不同,智能指针可以自动管理(动态)内存的分配和释放,使得程序员可以更方便地使用动态内存并减少对内存泄漏和空指针解引用等问题的担忧。
智能指针通常具有共享所有权或独占所有权,以及针对特定类型的安全访问函数,如引用计数和范围检查。
简而言之,智能指针是对裸指针(即普通指针)的封装,在安全性与性能两方面做权衡,最大限度的减少程序员使用动态内存的心智负担。如C++标准库中提供了多种类型的智能指针,例如unique_ptr、shared_ptr和weak_ptr。即使不使用c++作为主力开发语言,如c语言开发者,了解和学习智能指针也可以提高自己的编程水平,在特定的应用场景下封装出适合的智能指针,提高工程整体的可读性与稳定性。
常见智能指针及其特点
unique_ptr独占对象的所有权,由于没有引用计数,因此性能较好。
shared_ptr共享对象的所有权,但性能略差。
weak_ptr配合shared_ptr,解决循环引用的问题。
shared_ptr
内存模型
指针域指向内存对象首地址
控制域中包含引用计数,即当前这个堆上对象被多少对象引用了
weak弱计数
以及其他内容,如目标内存释放接口,内存扩容接口等
std::shared_ptr使用引用计数,每一个shared_ptr的拷贝都指向相同的内存。在最后一个shared_ptr析构的时候,内存才会被释放。
shared_ptr共享被管理对象,同一时刻可以有多个shared_ptr拥有对象的所有权,当最后一个shared_ptr对象销毁时,被管理对象自动销毁。
使用举例
使用智能指针可以自动释放占用的内存
{
shared_ptr<Buffer> buf = make_shared<Buffer>("auto free memory"); // Buffer 对象分配在堆上,但能自动释放
Buffer *buf = new Buffer("auto free memory");// Buffer对象分配在堆上,但需要手动delete释放
}
共享所有权指针的传播和释放
基本用法和常用函数
s.get():返回shared_ptr中保存的裸指针;
s.reset(…):重置shared_ptr;
reset( )不带参数时,若智能指针s是唯一指向该对象的指针,则释放,并置空。若智能指针P不是唯一指向该对象的指针,则引用计数减少1,同时将P置空。reset( )带参数时,若智能指针s是唯一指向对象的指针,则释放并指向新的对象。若P不是唯一的指针,则只减少引用计数,并指向新的对象。
使用shared_ptr要注意的问题
- 不要用一个原始指针初始化多个shared_ptr
int *ptr = new int;
shared_ptr<int> p1(ptr);
shared_ptr<int> p2(ptr); // 逻辑错误
- 不要在函数实参中创建shared_ptr
function(shared_ptr<int>(new int), g()); //有缺陷
因为C++的函数参数的计算顺序在不同的编译器不同的约定下可能是不一样的,一般是从右到左,但也可能从左到右,所以,可能的过程是先new int,然后调用g(),如果恰好g()发生异常,而shared_ptr还没有创建, 则int内存泄漏了,正确的写法应该是先创建智能指针,代码如下:
shared_ptr<int> p(new int);
function(p, g());
-
通过shared_from_this()返回this指针
不要将this指针作为shared_ptr返回出来,因为this指针本质上是一个裸指针,因此,这样可能会导致重复析构; -
避免循环引用
unique_ptr
独占的智能指针
- unique_ptr是一个独占型的智能指针,不能将其赋值给另一个unique_ptr
- unique_ptr可以指向一个数组
- unique_ptr需要确定删除器的类型
使用方法
基本与shared_ptr一致,只不过不能多个unique_ptr指向同一个内存对象,从而规避了很多问题,但是也少了很多使用场景。
weak_ptr
弱引用的智能指针
什么是weak_ptr
share_ptr虽然已经很好用了,但是有一点share_ptr智能指针还是有内存泄露的情况,当两个对象相互使用一个shared_ptr成员变量指向对方,会造成循环引用,使引用计数失效,从而导致内存泄漏。
weak_ptr解决了什么问题
weak_ptr 是一种不控制对象生命周期的智能指针, 它指向一个 shared_ptr 管理的对象. 进行该对象的内存管理的是那个强引用的shared_ptr, weak_ptr只是提供了对管理对象的一个访问手段。
weak_ptr为什么能解决问题。
weak_ptr 设计的目的是为配合 shared_ptr 而引入的一种智能指针来协助 shared_ptr 工作, 它只可以从一个 shared_ptr 或另一个 weak_ptr 对象构造, 它的构造和析构不会引起引用记数的增加或减少。
使用方法
通过use_count()方法获取当前观察资源的引用计数
shared_ptr<int> sp(new int(10));
weak_ptr<int> wp(sp);
cout << wp.use_count() << endl; //结果讲输出1
通过expired()方法判断所观察资源是否已经释放
shared_ptr<int> sp(new int(10));
weak_ptr<int> wp(sp);
if(wp.expired())
cout << "weak_ptr无效,资源已释放";
else
cout << "weak_ptr有效";
通过lock方法获取监视的shared_ptr
std::weak_ptr<int> gw;
void f()
{
auto spt = gw.lock();
if(gw.expired()) {
cout << "gw无效,资源已释放";
}
else {
cout << "gw有效, *spt = " << *spt << endl;
}
}
int main()
{
{
auto sp = std::make_shared<int>(42);
gw = sp;
f();
}
f();
return 0;
}
weak_ptr解决循环引用问题
在shared_ptr章节提到智能指针循环引用的问题,因为智能指针的循环引用会导致内存泄漏,可以通过weak_ptr解决该问题,只要将A或B的任意一个成员变量改为weak_ptr
#include <iostream>
#include <memory>
using namespace std;
class A;
class B;
class A {
public:
std::weak_ptr<B> bptr; // 修改为weak_ptr
~A() {
cout << "A is deleted" << endl;
}
};
class B {
public:
std::shared_ptr<A> aptr;
~B() {
cout << "B is deleted" << endl;
}
};
int main()
{
{
std::shared_ptr<A> ap(new A);
std::shared_ptr<B> bp(new B);
ap->bptr = bp;
bp->aptr = ap;
}
cout<< "main leave" << endl;
return 0;
}
这样在对B的成员赋值时,即执行bp->aptr=ap;时,由于aptr是weak_ptr,它并不会增加引用计数,所以ap的引用计数仍然会是1,在离开作用域之后,ap的引用计数为减为0,A指针会被析构,析构后其内部的bptr的引用计数会被减为1,然后在离开作用域后bp引用计数又从1减为0,B对象也被析构,不会发生内存泄漏。