智能指针之使用

上一章:智能指针 (3)

目录

有了智能指针的定义,我们现在来讲讲智能指针如何使用优势以及一些问题。
1,unique_ptr

具有拥有语义的类成员变量
传统情况下,具有拥有语义类成员变量可使用:普通成员,普通指针。

普通成员变量, 需要在头文件里面包含所拥有成员的头文件,这会增加编译的复杂度。

//Test1.h
#include "Test1.h"
class Test1
{
    Type1 ownedByTest1;
public:
    Test1();
    ~Test1();
};

//Test1.cpp
#include "Test.h"
#include "Type1.h"
Test1::Test1()
{}
    
Test1:: ~Test1()
{}

普通指针, 只需要前置声明,在cpp文件里面包含成员的头文件即可,这样不会增加编译的复杂度,只需要增加链接即可,但是这种裸指针,必须在函数析构时显示的删除,在项目庞大,结构复杂的情况下,程序员就有可能犯错忘记删除资源,造成资源泄露。

//Test2.h
class Type2;
class Test2
{
    Type2* ownedByTest2;
public:
    Test2();
    ~Test2();
};

//Test2.cpp
#include "Test.h"
#include "Type2.h"
Test2::Test2()
    : ownedByTest2(new Type2)
{}
    
Test2:: ~Test2()
{
    delete ownedByTest2;
}

unique_ptr综合了普通成员变量和指针成员变量的优点,又没有他们的缺点。
(1) unique_ptr较普通成员,主要是通过前置声明的方式减少头文件包含。
(2) unique_ptr普通指针,可以去掉析构时显式delete成员。

// Test3.h
#include <memory>
class Type3;
class Test3
{
    std::unique_ptr<Type3> ownedByTest3;
};

// Test3.cpp
Test3::Test3()
     : ownedByTest3(new Type3)
{}

Test3::~Test3()
{}

函数参数传递
其意义是通过move语意,把内存的所有权转移。
对于C++11之前,auto_ptr也有这个功能,只是auto_ptr语义没有那么明确。造成很多没有理解正确的人滥用auto_ptr,造成程序不可预期的结果。
C++11之后,auto_ptr被deprecated了,取而代之的是unique_ptr,而对于unique_ptr,其语义非常明确,必须要通过std::move 进程所有权转移。

void doSomething(std::unique_ptr<Type> type)
{
    // do something
}
void moveOnwerShip(std::unique_ptr<Type> type)
{
    doSomething(type); // 错误, 编译无法通过
    doSomething(std::move(type)); // 正确, OwnerShip转移
}

2, shared_ptr

具有关联属性的类成员变量,即有多个对象都关联此成员。
具有关联属性的类成员变量可使用:引用, 普通指针。

class Test1
{
    Type1& type1_;
public:
    Test1(Type1& type1);
};

Test1::Test1(Type1& type1)
    :type1_(type1)
{}

class Test2
{
    Type2* type2_;
public:
    Test2(Type1* type2);
};

Test2::Test2(Type2* type2)
    :type2_(type2)
{}

使用引用和指针,要非常注意的是在类对象的析构顺序,如果用这些引用或指针的对象被析构掉后,那么这些指针或引用就无效了,如果此时被引用关联的对象再去使用,就会产生资源错误(比如段错误)。

要解决类对象析构顺序问题,就必须用到一种技术——引用计数(比如Com指针),而C++11之后,标准库里面的shared_ptr为我们提供了这一解决方案。
(1) shared_ptr较引用,可以不用考虑引用被关联对象析构的顺序。也就是说,对于引用被关联对象,必须在引用对象析构前析构。否则引用对象一旦析构,医用就即可失效,引用使用就会引起内存错误。而shared_ptr只有在引用计数为0(即最后一份引用被销毁)时,才会去真正销毁资源。

// 只有在其他地方的share_ptr 都被销毁了,在Test析构的时候才会去销毁Type资源。
class Test
{
    std::shared_ptr<Type> type_;
public:
    Test(std::shared_ptr<Type> type);
};

Test::Test(std::shared_ptr<Type> type)
    :type_(type)
{}

(2) shared_ptr较普通指针,普通指针存在和引用同样的问题,因此shared_ptr有着同样的优势。

函数参数传递
其传递具有引用或指针传递的优势,并且无需担心对象是否有效,因为shared_ptr所指向的对象一直都是有效的(只要初始化时是有效的),share_ptr每次赋值,只是引用计数+1,销毁时,只是引用计数-1,因此参数传递时,没有太多的额外开销。

3, 作用域问题

如果在某个作用域里面,有多个分支要手动销毁对象,那么智能指针便是最好的选择,不会因为漏写而产生内存泄漏。

比如如下代码,非常容易出错,若以后case还要增加,则情况会变得更加复杂。

void mutiStatesReturn(const State& state)
{
    Type* type = new Type();
    switch(state)
    {
    case STATE1:
        handle1();
        delete type;
        return;
    case STATE2:
        handle2(type);
        break;
    default:
        break;
    }
    handle3(type);
    delete type;
}

如果用share_ptr, 则情况就会变得简单多,无需考虑type的资源释放问题:

void mutiStatesReturn(const State& state)
{
    std::shared_ptr<Type> type(new Type());
    switch(state)
    {
    case STATE1:
        handle1();
        return;
    case STATE2:
        handle2(type);
        break;
    default:
        break;
    }
    handle3(type);
}

4, shared_ptr存在的问题
(1)首先就是环形引用问题,即A1引用A2,A2引用A3 ..... Ax引用A1,这个问题不在累赘,解决方案是用weak_ptr.
(2)正所谓成也萧何,败也萧何,shared_ptr为了解决对象销毁的顺序问题,但是也正是它可以解决这个问题,导致它被滥用。

比如有三个对象同时引用了一个shared_ptr, 那么通过代码走读,我们很容易就能知道他们之间的关系,谁先创立,谁后销毁。

但是试想一下,如果有30个对象同时引用一个shared_ptr,那么,很难搞清楚个中关系,这个对维护的人来说,简直就是shit。

在无shared_ptr时代,一般建立对象有着严格的顺序,如下方式

启动模块1
@启动子模块11
@@创建对象111
@@创建对象112
......
@启动子模块12
......
启动模块2
@启动子模块21
.......
@启动子模块22
......

层次非常分明,因为如果随意创建,并且有多对象引用同一资源,那么必然会造成资源多次释放。

而有了智能指针之后,很多人就不遵循层次化的原则, 写的人轻松,看的人头大骂娘。

因此个人观点是,即使有引用计数的智能指针,还是要有明确的先后次序,能用引用代替shared_ptr,则应该代替,shared_ptr到处存, 单例到处创建要不得(单例这个问题,以后有机会专门讲讲, 它最大的缺点就是初始化和销毁顺序不明确)。

下一章: 容器 - vector (1)
目录

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