C++11:智能指针类

1、介绍

C++11新标准有三个智能指针类:shared_ptr、unique_ptr和weak_ptr。这三个类都在头文件memory中。

智能指针的作用主要是防止内存泄露,使用智能指针可以防止用new申请内存但是忘记delete从而造成内存泄露。使用智能指针,内存在不使用的时候,会自动释放。

2、shared_ptr

shared_ptr允许有多个shared_ptr指向同一块内存,有n个shared_ptr指向同一块内存,引用计数就为n,当n = 0的时候,shared_ptr就会释放所使用的内存。

声明一个shared_ptr:
shared_ptr<string> sp1;//sp1可以指向一个string
shared_ptr<vector<int>> sp2;//sp2可以指向一个vector<int>

智能指针的使用方式和普通指针一样,解指针引用返回它指向的对象,在条件判断语句中使用它,就是判断其是否为空。

if(sp2 && sp2->empty())
{
  (*sp2).push_back("123");//向sp2指向的vector中加入string"123"
}
shared_ptr的常用函数:
sp.get()   /*返回sp中保存的指针,可以是内置指针类型*/
swap(p, q) /*交换p和q中指向的指针,非成员函数*/
p.swap(q)  /*交换p和q中指向的指针,是智能指针类的成员函数*/
shared_ptr<T> p(q)  /*p是q的拷贝,这会增加q的引用计数*/
p = q  /*p和q都是shared_ptr,或者能够相互转换,此操作会递减p的引用计数,增加q的引用计数*/
p.unique()  /*若p的共享对象数量为1,则返回true,否则为false*/
p.use_count()  /*返回与p共享对象的智能指针数量*/
make_shared函数:

shared_ptr最安全分配和使用动态内存的方法是使用库函数make_shared,这个函数是在动态内存中分配一块内存并初始化它,返回这个对象的shared_ptr。

shared_ptr<int> p1 = make_shared<int>();
shared_ptr<int> p2 = make_shared<int>(222);
shared_ptr<string> p3 = make_shared<string>(10, '3');
auto p4 = make_shared<string>(10, '4');
shared_ptr的引用计数:

引用计数递增的情况:
(1)用一个shared_ptr初始化另一个shared_ptr
(2)赋值
(3)将一个shared_ptr作为一个参数传递给一个函数
(4)作为一个函数的返回值
引用计数递减的情况:
(1)给一个shared_ptr赋新值
(2)shared_ptr被销毁(如离开函数作用域)

当一个shared_ptr的引用计数为0的时候,就会调用析构函数销毁对象,并释放其占用的内存。

shared_ptr的通过引用计数这样的机制来实现内存的释放,可以用来实现类对象成员的数据共享。若我们一个类对象需要共享同一份数据,我们可以声明成static,而用shared_ptr也可以实现。

shared_ptr和new结合使用:

new运算符返回的指针不能转化成一个智能指针,因此要使用直接初始化。

shared_ptr<int> p = new int(1024);//错误
shared_ptr<int> p(new int(1024));//正确

智能指针默认使用delete来释放它所关联的对象,因此如果new出来的对象的释放操作不是delete,则需要定义自己的释放操作。

shared_ptr<T> p(q, d) //p接管了内置指针q指向的对象的所有权,p将会使用可调用对象d释放指向的对象
shared_ptr<T> p(p2, d) //p是p2的拷贝,唯一的区别是p会使用d来释放对象
p.reset()  //如果p的引用计数是1,则释放对象;否则p就只是置为空
p.reset(q)  //如果p的引用计数是1,则释放对象并指向q;否则就只是指向q
p.reset(q, d)  //和上的操作的唯一区别是会调用d来释放q

3、unique_ptr

unique_ptr“拥有”它所指向的对象,某个时刻只有一个unique_ptr指向一个给定的对象,因此一个unique_ptr不支持普通的拷贝或者赋值操作。

声明一个unique_ptr:
unique_ptr<int> p1;
unique_ptr<int> p2(new int(1024));
unique_ptr<string> p3(new string("1024"));
unique_ptr<int> p4(p2);  //错误,unique_ptr不支持拷贝
p1 = p2;  //错误,unique_ptr不支持赋值
unique_ptr的常用函数:
unique_ptr<T, D> u; //unique_ptr指向的对象的类型为T,将会调用类型为D的可调用对象来释放对象
unique_ptr<T, D> u(d);  //unique_ptr指向的对象的类型为T,将会调用类型为D的可调用对象d代替delete来释放对象
u.release();  //u放弃对指针的控制权,返回指针,并将u置空
u.reset();  //释放u指向的对象
u.reset(q);  //释放u指向的对象,并指向这个对象(q指向的)
u.reset(nullptr);  //u释放指针的所有权,指向nullptr

使用release函数和reset函数可以转移指针的所有权。

unique_ptr<string> p1(new string("p1"));
unique_ptr<string> p2(new string("p2"));
p2.reset(p1.release());  //p1释放对指针的所有权,并置为空;p2释放掉所指向的对象,指向p1指向对象
函数返回unique_ptr:

我们可以拷贝或者赋值一个将要被销毁的unique_ptr

//从int*创建一个unique_ptr返回
unique_ptr<int> clone(int p)
{
  return unique_ptr<int>(new int(p));
}
//返回局部对象的拷贝
unique_ptr<int> clone(int p)
{
  unique_ptr<int> ret(new int(p));
  return ret;
}
向unique_ptr传递删除器:

unique_ptr中也是默认使用delete进行释放对象,我们可以向unique_ptr指定删除器。
调用格式:

connection c = connect(&d);
unique_ptr<connection,  decltype(end_connection)*> p(&c, end_connection);

尖括号中的两个参数是类型,其中decltype(end_connection)返回一个函数类型,所以必须添加一个*号指出使用的类型是函数指针。

3、weak_ptr

weak_ptr不控制所指向对象的生存周期,它指向一个由shared_ptr管理的对象,并且不会改变其引用计数,因此即使有weak_ptr指向对象,shared_ptr的引用计数为0的时候还是会释放对象。

声明一个weak_ptr

当我们要创建一个weak_ptr的时候,需要用一个shared_ptr去初始化它。

auto sp = make_shared<int>(100);
weak_ptr<int> wp(sp);

weak_ptr常用函数

wp = sp; //sp可以是shared也可以是weak
w.reset();  //将w置空
w.use_count(); //返回与w共享对象的shared_ptr的数量
w.expired();  //如果w.use_count()为0,返回true,否则返回false
w.lock();  //如果w.expired()为true,返回一个空shared_ptr,否则返回一个指向w对象的shared_ptr

由于weak_ptr指向的对象可能不存在,所以不能直接访问,需要通过lock函数检查对象是否仍然存在。

if(shared_ptr<int> sp = wp.lock())
{
  //访问对象
}

weak_ptr常用于作为一个核查指针。

4、智能指针和动态数组

内存泄露常见的情况是使用new申请了一个比较大的动态数组,但是忘记了delete,造成了比较大的内存泄露问题,使用智能指针指向动态数组,可以防止这个问题。

标准库提供了管理new分配的数组的unique_ptr版本,使用方式如下:

unique_ptr<int[]> up(new int[1024]);
for(int i = 0; i<1024; ++i)
{
  up[i] = i;//此时箭头和.号没有作用,因为up指向的是一个数组
}
up.release();//up会自动调用delete[]释放数组

使用shared_ptr也可以管理动态数组,但是标准库没有提供支持,所以需要使用shared_ptr管理动态数组的时候需要提供自定义的删除器。

shared_ptr<int[]> sp(new int[1024], [](int* p) {delete[] p;});
for(int i = 0; i<1024; ++i)
{
  *(sp.get() + i) = i; //因为sp内置不支持管理动态数组,因此这里的使用需要先解引用
}
sp.reset();//调用reset的时候就使用我们定义的lambda表达式释放动态数组

智能指针和异常:

使用智能指针的时候,如果程序块过早结束,智能指针也能在内存不需要的时候释放

使用内置指针的时候:

void f()
{
  int *p = new int[100];
  //代码抛出异常,并且没有被捕获
  delete[] p;
}
//这样指针p指向的内存会得不到释放

使用智能指针可以确保上述情况内存得到释放

void f()
{
  shared_ptr<int[]> sp(new int[100], [](int* p) {delete[] p;});
  //代码抛出异常,并且没有被捕获,内存会自动释放
}

不要混用智能指针和普通指针:

混用智能指针和内置指针,容易造成程序错误。

void process(shared_ptr<int> sp)
{
  cout << *sp << endl;
}//sp离开作用域,被销毁

int main()
{
  int* x(new int(1024));
  process(shared_ptr<int>(x)); //传入函数的时候,sp的引用计数是1,离开作用域之后,变为0,会释放内存,此时x指向的内存已经被释放
  int j = *x;  //错误,x是野指针
}

总结

尽量使用智能指针代替内置指针,千万不要智能指针和内置指针混用。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,219评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,363评论 1 293
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,933评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,020评论 0 206
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,400评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,640评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,896评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,597评论 0 199
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,327评论 1 244
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,581评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,072评论 1 261
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,399评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,054评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,083评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,849评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,672评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,585评论 2 270

推荐阅读更多精彩内容

  • 导语: C++指针的内存管理相信是大部分C++入门程序员的梦魇,受到Boost的启发,C++11标准推出了智能指针...
    7ee72f98ad17阅读 818评论 0 1
  • C++裸指针的内存问题有:1、空悬指针/野指针2、重复释放3、内存泄漏4、不配对的申请与释放 使用智能指针可以有效...
    WalkeR_ZG阅读 3,003评论 0 5
  • 原作者:Babu_Abdulsalam 本文翻译自CodeProject,转载请注明出处。 引入### Ooops...
    卡巴拉的树阅读 29,940评论 13 74
  • C#、Java、python和go等语言中都有垃圾自动回收机制,在对象失去引用的时候自动回收,而且基本上没有指针的...
    StormZhu阅读 3,614评论 1 15
  • 出来是实习一个月了,现在已经开始习惯现在的生活了,不明白自己能不能留在公司里面呢继续上班,虽然很希望留下来,但是我...
    婷婷婷哥阅读 160评论 0 0