【游戏技术群】959392658  【游戏出海群】12067810
yzc88亚洲城 手机端
  • 开发者工具开发者工具
  • www.yzc88.comManual
  • 蛮牛杯蛮牛杯
  • Unity官方专区Unity官方专区
开发者专栏

关注:2422

当前位置:yzc88亚洲城 技术专区 开发者专栏

__________________________________________________________________________________
开发者干货区版块规则:

  1、文章必须是图文形式。(至少2幅图)
      2、文章字数必须保持在1500字节以上。(编辑器右下角有字数检查)
      3、本版块只支持在yzc88亚洲城原创首发,不支持转载。
      4、本版块回复不得无意义,如:顶、呵呵、不错......【真的会扣分的哦】
      5、......
__________________________________________________________________________________
查看: 1569|回复: 23
发新帖

[士郎] 智能指针的正确实践

[复制链接]  [移动端链接]
排名
1
昨日变化

7318

主题

7860

帖子

3万

积分

Rank: 16

UID
1231
好友
185
蛮牛币
8818
威望
30
注册时间
2013-7-29
在线时间
3742 小时
最后登录
2019-2-22

社区QQ达人活力之星原创精华达人突出贡献奖财富之证yzc88亚洲城QQ群会员蛮牛妹VIP

跳转到指定楼层
楼主
yzc88亚洲城 2019-1-24 20:48:25 | 只看该作者 |只看大图 回帖奖励 |倒序浏览 |阅读模式

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有帐号?注册帐号

x
早在1994年,Gregory Colvin就向C++标准委员会提出了智能指针的提案(Smart Pointers - 1.54.0)。但早期的设计并不好用。各个库都有自己的一套智能指针,没有标准化。经过20多年的发展,特别是C++11标准引入shared_ptr和unique_ptr之后,智能指针技术趋于成熟。然而在实践中,大多数项目还在使用自己山寨的引用计数解决方案。智能指针还没有成为C++程序员的常备技能。


像其他技术一样,智能指针有一定学习成本,如果被误用,同样也会带来各种bug。本文简要回顾C++11智能指针shared_ptr/unique_ptr/weak_ptr的核心概念,并试图总结其正确使用方法。


Ownership Logic
正确使用智能指针的前提是搞清楚业务逻辑需要。其中最重要的是设计资源管理,即ownership,并据此选择是否使用智能指针,使用哪种智能指针。智能指针有其内在的ownership logic。


所谓own某个指针,意味着有责任在合适的时候释放该指针。获得、引用和使用某个指针,并不一定需要负责释放该指针所指向的资源。
shared_ptr是shared ownership,owner发起释放操作,只是减引用计数,只有所有owner都释放,所指向的对象才真正释放。
weak_ptr不控制对象的生命周期,但是它观察着shared_ptr管理的对象,有办法知道对象是否还活着。
unique_ptr则是unique ownership,对象的管理权可以转移,但是同一时刻只有一个owner,否则编译就会报错。


shared_ptr, weak_ptr
shared_ptr在底层使用了两个技术,一个是引用计数,另一个是引入了一个中间层(http://umich.edu/~eecs381/handouts/C++11_smart_ptrs.pdf, Be Smart About C++11 Smart Pointers)。


为了管理目标对象,所创建的中间层被称为manager object。其中除了目标对象的裸指针,还有两个引用计数。一个用于shared_ptr,一个用于weak_ptr。当shared count减到0的时候,managed object就会被销毁。只有shared count和weak count都减到0的时候,manager object才会被销毁。





shared_ptr/weak_ptr原理enable_shared_from_this
如果涉及到将this指针提升为shared_ptr的情况,直接提升会新建一个manager object。
[AppleScript] 纯文本查看 复制代码
void f(shared_ptr<Thing>);
class Thing {
public:
    void foo() {
        //f(shared_ptr<Thing>(this));   //new manager object A
        f(shared_from_this());  //use manager object B
    }
};
int main() {
    shared_ptr<Thing> sp(new Thing());  //new manager object B
    sp->foo();
}

使用两个manager object管理同一个对象会造成不可预知的后果。为避免这种情况,需要在对象中维护一个weak_ptr。这是通过enable_shared_from_this自动完成的。




enable_shared_from_this原理

当需要在object内部使用this指针时,调用shared_from_this()就可以避免新建manager object。需要注意的是,在构造函数中,对象还未构造完毕,并没有交由shared_ptr管理,即manager object还未创建,所以不能使用shared_from_this。



unique_ptr
unique_ptr是对裸指针的简单封装,不需要额外的manager object。和shared_ptr基本用法一致,只是unique_ptr没有引用计数,内部指针要么有效,要么没有。


unique_ptr可以用(unique_ptr rvalue)初始化,但不允许copy construction或copy assignment。这符合其unique ownership语义。所以如果函数参数中有unique_ptr,应该传引用或指针,不能传值。


[AppleScript] 纯文本查看 复制代码
unique_ptr<Thing> create() {
    unique_ptr<Thing> ptr(new Thing);
    return ptr; //rvalue
}
unique_ptr<Thing> ptr2(create());
//unique_ptr<Thing> ptr3(ptr2);  //copy construction
//unique_ptr<Thing> ptr3 = ptr2; //copy assignment




Race Condition
c++20将有atomic_shared_ptr/atomic_weak_ptr,这是不是意味着shared_ptr/weak_ptr并不是线程安全的呢?
一般地讲,manager object中的引用计数增减是原子操作,所以是线程安全的。同时读shared_ptr也是安全的。但是如果有线程在读写同一个shared_ptr,就不是安全的(shared_ptr - 1.57.0),这和操作一般指针是一致的。如果两个线程在操作两个shared_ptr,即使他们指向同一个manager object,只要没有访问所管理的对象,就是安全的(Lesson #4: Smart Pointers)。



[AppleScript] 纯文本查看 复制代码
shared_ptr<int> p(new int(42));

// thread A
shared_ptr<int> p2(p); // reads p
p2.reset(new int(1912)); //writes p2

// thread B
shared_ptr<int> p3(p); // OK, multiple reads are safe
p3.reset(); // OK, writes p3

需要指出的是,从weak_ptr.lock()提升为shared_ptr是线程安全的。unique_ptr的所有权转让也是安全的。但是使用unique_ptr操作对象是不安全的(Atomic Smart Pointers)。


智能指针正确用法
使用shared_ptr/weak_ptr/unique_ptr必须要注意:

1)必须保证所有managed object只有一个manager object
当object第一次创建的时候,就要立刻交由一个shared_ptr管理,其他的shared_ptr和weak_ptr都必须从第一个shared_ptr拷贝或赋值得到。具体来说,就是调用shared_ptr(raw ptr)和make_shared函数。


[AppleScript] 纯文本查看 复制代码
class Thing;
shared_ptr<Thing> p1(new Thing);
shared_ptr<Thing> p2(make_shared<Thing>());

其中使用make_shared一次性分配managed object和manager object两块内存,效率更高。而且对于强迫症来说,看到new看不到delete总是感觉挺难受的,还不如连new都不要看到。


2)能用裸指针解决问题的情况下,就不要使用智能指针。
如果决定了用智能指针,那就不要用裸指针管理同一个对象。


虽然通过智能指针get()函数可以得到裸指针,但是尽量不要用。对象一经创建,就应该纳入shared_ptr/unique_ptr的管理之下。为了保证没有裸指针暴露出来,应该只用shared_ptr(raw ptr)/unique_ptr(raw_ptr)和make_shared/make_unique函数创建对象。
3)ownership logic是正确使用智能指针的基础,不要滥用shared_ptr


weak_ptr是为了解决循环引用而引入的。当系统中出现了循环引用,且都是使用shared_ptr管理对象,那么一定是shared_ptr被滥用了。
weak_ptr观察着shared_ptr管理的对象,必须从shared_ptr或weak_ptr创建。其唯一正确的使用方法是先用lock()调用提升为shared_ptr,然后使用shared_ptr。如果直接用shared_ptr(weak_ptr)来构造,可能会在weak_ptr已经expire的情况下抛出异常。
shared_ptr可以用==,!=,<来比较,实际比较的是他们管理的裸指针。


shared_ptr有拷贝开销,作为参数时,应该尽量用const reference。
4) 能用unique_ptr管理的对象,不要使用shared_ptr/weak_ptr
这其实是说尽量在单一的固定地方管理资源,如果不能保证ownership固定,可以转移所有权,尽量保证只有一个owner(An overview on smart pointersGoogle C++ Style GuideTop 10 dumb mistakes to avoid with C++ 11 smart pointers - A CODER'S JOURNEY)。
最佳实践实例
1)main thread拥有对象,加载子线程只管加载。
[AppleScript] 纯文本查看 复制代码
//main thread
class Thing;
shared_ptr<Thing> p1(make_shared<Thing>());
if (p1->getState() == eLoadFinish) ... //use after loading finish

//loading thread
weak_ptr<Thing> p2(p1);
shared_ptr<Thing> p3 = p2.lock();
if (p3 && p3->getState() != eLoadFinish)
{
    ... //loading
    p3->setState(eLoadFinish); //set loading finish flag
}

此处不宜使用unique_ptr。虽然在子线程的加载过程中可以上锁,但是对象中途若被主线程释放,将会宕机。
2)拥有者和使用者都在main thread,使用者需要定期对对象做操作。


[AppleScript] 纯文本查看 复制代码
class Thing;
void funcUsePtr(const shared_ptr<Thing> &p){
    p->xxx(); //method call
}
void funcPassToUser(shared_ptr<Thing> &p){
    pUser = p;
}

//owner
shared_ptr<Thing> p1(make_shared<Thing>());  
funcUsePtr(p1); //normal use
funcPassToUser(p1);  //pass to pUser

//user
shared_ptr<Thing> p2 = pUser.lock();
if (p2)
    p2->yyy(); //method call



此处虽然只有一个线程,但也不宜用unique_ptr。试想owner如果中途要释放对象,user是不知道的。此时用weak_ptr维持一个弱引用,当需要的时候检查一下有效性,是比较合理的。


知乎@realTOM

[发帖际遇]: 一个袋子砸在了 清风 头上,清风 赚了 1 蛮牛币. 幸运榜 / 衰神榜

跟我念“站长妹纸萌萌哒!”我说站长,你说YO!爱你们么么哒~
6蛮牛粉丝
1421/1500
排名
4444
昨日变化
35

0

主题

844

帖子

1421

积分

Rank: 6Rank: 6Rank: 6

UID
254705
好友
1
蛮牛币
1379
威望
0
注册时间
2017-11-16
在线时间
247 小时
最后登录
2019-2-22
沙发
yzc88亚洲城 2019-1-25 08:30:58 | 只看该作者
6666666666666666666666666666666666

2初来乍到
115/150
排名
13600
昨日变化
264

0

主题

23

帖子

115

积分

Rank: 2Rank: 2

UID
308426
好友
0
蛮牛币
262
威望
0
注册时间
2018-12-16
在线时间
24 小时
最后登录
2019-2-23
板凳
yzc88亚洲城 2019-1-25 09:06:45 | 只看该作者
啊啊啊啊啊啊啊啊啊

6蛮牛粉丝
1435/1500
排名
1921
昨日变化
7

10

主题

235

帖子

1435

积分

Rank: 6Rank: 6Rank: 6

UID
252607
好友
5
蛮牛币
18304
威望
0
注册时间
2017-11-5
在线时间
488 小时
最后登录
2019-2-22

一掷千金

地板
yzc88亚洲城 2019-1-25 09:07:52 | 只看该作者

5熟悉之中
538/1000
排名
16821
昨日变化
8

1

主题

202

帖子

538

积分

Rank: 5Rank: 5

UID
213085
好友
5
蛮牛币
401
威望
0
注册时间
2017-3-20
在线时间
295 小时
最后登录
2019-2-20
5#
yzc88亚洲城 2019-1-25 09:11:47 | 只看该作者

4四处流浪
392/500
排名
8424
昨日变化
76

0

主题

150

帖子

392

积分

Rank: 4

UID
300324
好友
0
蛮牛币
20
威望
0
注册时间
2018-10-17
在线时间
94 小时
最后登录
2019-2-23
6#
yzc88亚洲城 2019-1-25 09:49:50 | 只看该作者
站长妹纸萌萌哒!

5熟悉之中
776/1000
排名
3499
昨日变化
21

2

主题

112

帖子

776

积分

Rank: 5Rank: 5

UID
282159
好友
0
蛮牛币
1387
威望
0
注册时间
2018-5-21
在线时间
238 小时
最后登录
2019-2-22
7#
yzc88亚洲城 2019-1-25 10:31:12 | 只看该作者

排名
39555
昨日变化
27

0

主题

10

帖子

29

积分

Rank: 1

UID
153843
好友
0
蛮牛币
35
威望
0
注册时间
2016-6-27
在线时间
14 小时
最后登录
2019-1-29
8#
yzc88亚洲城 2019-1-25 17:49:45 | 只看该作者
好好好好好好
[发帖际遇]: zjwmyl 捡了钱没交公 蛮牛币 降了 1 . 幸运榜 / 衰神榜

7日久生情
1909/5000
排名
2587
昨日变化
12

8

主题

683

帖子

1909

积分

Rank: 7Rank: 7Rank: 7Rank: 7

UID
40014
好友
16
蛮牛币
2415
威望
0
注册时间
2014-8-15
在线时间
494 小时
最后登录
2019-2-21
QQ
9#
yzc88亚洲城 2019-1-26 10:27:18 | 只看该作者
智能指针的正确实践

排名
64379
昨日变化
36

0

主题

2

帖子

5

积分

Rank: 1

UID
312875
好友
0
蛮牛币
2
威望
0
注册时间
2019-1-25
在线时间
1 小时
最后登录
2019-1-26
10#
yzc88亚洲城 2019-1-26 12:00:59 | 只看该作者
指针我只用智能指针
[发帖际遇]: 一个袋子砸在了 1357017637 头上,1357017637 赚了 1 蛮牛币. 幸运榜 / 衰神榜

3偶尔光临
246/300
排名
14363
昨日变化
1

2

主题

33

帖子

246

积分

Rank: 3Rank: 3Rank: 3

UID
132879
好友
0
蛮牛币
4
威望
0
注册时间
2015-12-29
在线时间
155 小时
最后登录
2019-1-27
QQ
11#
yzc88亚洲城 2019-1-26 14:29:03 | 只看该作者
dawdawdawdawdawdawdawda
[发帖际遇]: yao970808 发帖时在路边捡到 2 蛮牛币,偷偷放进了口袋. 幸运榜 / 衰神榜

排名
39555
昨日变化
8177

0

主题

3

帖子

19

积分

Rank: 1

UID
312887
好友
0
蛮牛币
32
威望
0
注册时间
2019-1-25
在线时间
6 小时
最后登录
2019-2-23
12#
yzc88亚洲城 2019-1-26 15:06:57 | 只看该作者
[发帖际遇]: duzx 乐于助人,奖励 1 蛮牛币. 幸运榜 / 衰神榜

3偶尔光临
284/300
排名
9400
昨日变化
2

0

主题

74

帖子

284

积分

Rank: 3Rank: 3Rank: 3

UID
302002
好友
0
蛮牛币
17
威望
0
注册时间
2018-10-31
在线时间
86 小时
最后登录
2019-2-22
13#
yzc88亚洲城 2019-1-26 19:13:23 | 只看该作者

2初来乍到
115/150
排名
13600
昨日变化
264

0

主题

23

帖子

115

积分

Rank: 2Rank: 2

UID
308426
好友
0
蛮牛币
262
威望
0
注册时间
2018-12-16
在线时间
24 小时
最后登录
2019-2-23
14#
yzc88亚洲城 2019-1-27 08:58:22 | 只看该作者
aaaaaaaaaaaaaaaaaaaaa

3偶尔光临
227/300
排名
17686
昨日变化
4

0

主题

69

帖子

227

积分

Rank: 3Rank: 3Rank: 3

UID
96853
好友
0
蛮牛币
0
威望
0
注册时间
2015-5-1
在线时间
122 小时
最后登录
2019-1-28
QQ
15#
yzc88亚洲城 2019-1-27 12:53:30 | 只看该作者
666666666666

您需要登录后才可以回帖 登录 | 注册帐号

本版积分规则

快速回复 返回顶部 返回列表