??xml version="1.0" encoding="utf-8" standalone="yes"?> 本课E要求听课h员已l读q《Unix |络~程》,能写单的 TCP echo 服务?/p> 评地址Q?a title="http://boolan.com/course/4" >http://boolan.com/course/4 配套面Q?a >http://chenshuo.com/pnp 购买地址Q?a >http://e.jd.com/30149978.html 阅读效果Q?/p>
目前京东的阅d没有切白边功能,值得改进?/p>
]]>PC
iPad
先说说程序员Q应届生Q面试的一般过E,一轮面试(面对一C个面试官Q一般是四、五十分钟,面试官会问两三个~程问题Q通常是两大一)Q因此留l每个编E题的时间只?20 分钟。这 20 分钟不光是写代码Q还要跟面试官讨Z的答案ƈ解答提问Q比如面试官拿过你的{案U,问某一行代码如果修改会有什么后果。因此真正留l在U怸或白板上写代码的旉也就 10 分钟上下。本文给Z一个能?10 分钟旉在纸上写出来且不会有错的 String classQ强调正性及易实玎ͼ白板上写也不会错Q,不强调效率与功能完备?/p>
本文的配套代码位?https://github.com/chenshuo/recipes/blob/master/string/StringTrivial.h?/p>
全文Q?a title="https://chenshuo.googlecode.com/files/CppEngineering.pdf" >https://chenshuo.googlecode.com/files/CppEngineering.pdf
> 陈硕Q你好,
>
> 阅读了你的书Q很有收莗?br>> 但是没有在moduo的源代码里面扑ֈ实现U程模型11的例子。即one thread per loop + thread pool?br>> 谢谢?/p>
书第 173 图 6-14 下面的第一D话Q具体改动方法参考前一늚 diff?/font>
> 谢谢?br>>
> 另外TcpConnection和Channel的生命周期管理有炚w题?br>> TcpConnection如果已经被回收了Q其包含的Channel也已l被回收了。而这个时候在Channel::handleEvent()里面查tied_和tie_是危险的。因为其内存已经被回收了?br>>
> 如果用户保证TcpConnection被回收之后,不会再用Channel的裸指针Q则没有必要在TcpConnection::connectEstablished()中call tie().
TcpConnection 回收之前Q会调用 connectDestroyedQ其中调?channel_->remove();Q这样就不可能再会有 Channel::handleEvent() 被调用了?/font>
tie() 的作用是防止 Channel::handleEvent() q行期间?owner 对象析构Q导?Channel 本n被销毁?/font>
> > TcpConnection 回收之前Q会调用 connectDestroyedQ其中调?channel_->remove();Q这样就不可能再会有 Channel::handleEvent() 被调用了?br>> q个时候会不会有race conditionQ假讄在有两个active channelsQ处理头一个的时候回收TcpConnectionQ而第二个channel刚好对应q个connection?/p>
q时你没有办法强刉?TcpConnectionQ只能降低其引用计数Q所以不会有问题。你可以写段代码试试?/font>
> 另外底层的poller OS api是否保证unregister channel之后一定不会再有这个channel的事Ӟ会清I内核的已经qA的事仉列?
跟内核没关系QPoller class ?unregister channel 之后׃可能调用?handleEvent() 成员函数?/font>
> 那EPollPoller::fillActiveChannels()的改一改,“assert(it != channels_.end());”不再适用了,而且每次都个event都要查一ơmap。效率会有问题?/p>
assert() 只有?debug build 才执行,不会影响效率?br>再说每个 event 都要涉及 read/write {系l调用,开销比“查一?map”大得多Q优化这里是无用功?/strong>
> 但这个assert()不是invalid了吗Q你可能之前在unregister channel的时候已l从map里面remove掉了它?
q个 assert 是有效的Q你再想惟?/font>
> > tie() 的作用是防止 Channel::handleEvent() q行期间?owner 对象析构Q导?Channel 本n被销毁?/font>
> q个也不太make sense。仍然有race conditon。在Channel::handleEvent()拥有guard锁定ownner之前QChannel::handleEventQ?需要检查其tied_?/p>
你再xQtie 的作用是防止调用 handleEvent() 期间对象销毁(比如调用 closeCallback 期间Q,不是也不可能防止调用 handleEvent() 之前对象销毁?/strong>
> 恩,是的。整个TcpConnection, Channel, EventLoop都是一个thread里面run的?/font>
赖勇(http://laiyonghao.comQ?/p>
最q,有一位朋友因为工作需要,需要从|游的客L~程转向服务器端~程Q找我推荐一本书。我推荐了《Linux 多线E服务器端编E?#8212;—使用 muduo C++ |络库》给他,他在|上书店看了以后问我Z么推荐这么厚一本书l他Q正好这本书我已l早q完了Q一直也惛_?#8220;书评”Q就在这里多扯几句。其实实在算不上书评Q原因有二:一是读书的时候囫囵吞枣,理解不够深刻Q不深刻自然不能评;二是q几q虽然在 Linux 下写服务器端的网l程序,但很用多线E,也很用 C++Q书里谈的东西,是不熟悉的领域Q自然也不能p。所以今天这,应当是推荐,是ؓ陈硕老师背书?/p>
l箋阅读Q?/p>
http://blog.csdn.net/gzlaiyonghao/article/details/10366863
http://book.douban.com/review/6249351/
前几天,我发了一条微?http://weibo.com/1701018393/A7FrW7ZVd Q质疑某本书?Pthreads 条g变量的封装是错的Q因为它没有?mutex ?lock()/unlock() 函数暴露出来Q导致无法实用。后来大家讨论的分歧是这?cond class 是不是通用的条件变量封装,q是只是一个特D的“事g{待?#8221;。作Z件等待器Q其实现也是错的Q因为存在丢׃件的可能Q可以算是初学者用条件变量的典型错误?/p>
本文的代码位?recipes/thread/test/Waiter_test.ccQ这里提到的某书的版本相当于 Waiter1 class?/p>
我在拙作《Linux 多线E服务端~程Q?muduo C++ |络库》第 2.2 节ȝ了条件变量的使用要点Q?/p>
条g变量只有一U正用的方式Q几乎不可能用错。对?wait 端:
1. 必须?mutex 一起用,该布表辑ּ的读写需受此 mutex 保护?br />2. ?mutex 已上锁的时候才能调?wait()?br />3. 把判断布条件和 wait() 攑ֈ while 循环中?/font>
对于 signal/broadcast 端:
1. 不一定要?mutex 已上锁的情况下调?signal Q理ZQ?br />2. ?signal 之前一般要修改布尔表达式?br />3. 修改布尔表达式通常要用 mutex 保护Q至用?full memory barrierQ?br />4. 注意区分 signal ?broadcastQ?#8220;broadcast 通常用于表明状态变化,signal 通常用于表示资源可用。(broadcast should generally be used to indicate state change rather than resource availability。)”
如果用条件变量来实现一?#8220;事g{待?Waiter”Q正的做法是怎样的?我的最l答案见 WaiterInMuduo class?#8220;事g{待?#8221;的一U用途是E序启动时等待初始化完成Q也可以直接?muduo::CountDownLatch 到达相同的目的,初D?1 卛_?/p>
以下Ҏ微博上的讨论q程l出几个正确或错误的版本Q博大家一W?font color="#0000ff">只要C Pthread 的条件变量是Ҏ触发Qedge triggerQ,?signal()/broadcast() 只会唤醒已经{在 wait() 上的U程(s)Q?/font>我们在编码时必须要考虑 signal() 早于 wait() 的可能,那么很Ҏ判断以下各个版本的正误了。代码见 recipes/thread/test/Waiter_test.cc?/p>
版本一Q?font color="#ff0000">错误。某书上的原始版Q有丢失事g的可能?/p>
版本二:错误。lock() 之后?signal()Q同h丢失事g的可能?/p>
版本三:错误。引入了 bool signaled_; 条gQ但没有正确处理 spurious wakeup?/p>
版本四五六:正确。仅?single waiter 使用?br />
版本七:最佟뀂可?multiple waiters 使用?br />
版本八:错误。存?data raceQ且有丢׃件的可能。理p http://stackoverflow.com/questions/4544234/calling-pthread-cond-signal-without-locking-mutex
ȝQ用条件变量,调用 signal() 的时候无法知道是否已l有U程{待?wait() 上。因此一般L要先修改“条g”Q其ؓ trueQ再调用 signal()Q这?wait U程先检?#8220;条g”Q只有当条g不成立时才去 wait()Q避免了丢事件的可能?span style="color: #333333; font-family: Verdana, Geneva, Arial, Helvetica, sans-serif; background-color: #ffffff;">换言之,通过使用“条g”Q将Ҏ触发Qedge triggerQ改为电q发(level triggerQ?/span>q里“修改条g”?#8220;查条?#8221;都必d mutex 保护下进行,而且q个 mutex 必须用于配合 wait()?/p>
思考题Q如果用两个 mutexQ一个用于保?#8220;条g”Q另一个专门用于和 cond 配合 wait()Q会出现什么情况?
最后注明一点,http://stackoverflow.com/questions/6419117/signal-and-unlock-order q篇帖子里对 spurious wakeup 的解释是错的Qspurious wakeup 指的是一?signal() 调用唤醒两个或以?wait()ing 的线E,或者没有调?signal() 却有U程?wait() q回。manpage 里对 Pthreads pd函数的介l非常到位,值得l读?/p>
q不?memcached 的替代品Q它没有实现LRU和超时功能,也没有实Cq制协议Q更没有自己理内存Q,而是一个网l编E的CZQ代码只?1000 行,?memcached 很多)Q展C?muduo 风格的事仉动编E,以及来性能优化的试验品Q换句话_现在q个版本完全没有在性能上做ZQ何努力)。读q?memcached 代码的h可以Ҏq两U编E风格的区别Qmemcached ?read/write 操作I插于正帔R辑处理Q?muduo 的网l数据读写是由库完成Q应用程序只兛_消息收发Q目前二者的基本 get/set 操作的性能相当?/p>
现在 muduo ?inspector 内置?gperftools 的远E?profiling 功能Qmemcached-debug 展示了其用法?/p>
Z么不必优?set 操作Q含 set/add/update/append/prepend/cas {)的性能Q?/p>
1. 比例。既然是 memcacheQ那?get:set 的比例很高,10:1 甚至更高Q因此优化的重心应该?get 而非 set?/p>
假设 memcached 能处?100k QPSQ再假设q些操作都是 setQ其实应该不?10% ?setQ,再假设所有的 set 都是串行执行的(没有q发Q,那么每次 set ?CPU 旉不应该超q?10 usQ含服务器本地的|络代码q行旉Q但不含|络延迟Q。而实际上一?set ?CPU 旉最多是 2~3 us Q用 memcached-footprint E序得Q,Ҏ不值得优化?/p>
2. |络带宽。假设一?set 操作?key + value 的长度是 1k bytesQTCP 的有效蝲荷带宽按110MB/s估算Q那?kB数据在千兆网上的惯性gq是 9usQ传输gq是几十上百微秒Q与此无养IQ也是说服务器的网卡收到这 1kB 数据需要花 9us 旉Q从W一个字节到辑ֈ服务器到收完最后一个字节)Q那么在 set 耗时 2~3 us 的情况下再去优化它是做无用功?/p>
3. 产生“需要更新的数据”的成本远大于 memcached set 的开销。memcached 需要更斎ͼ往往是将已写入数据库的新数据攑ֈ memcached 中,那么写数据库的开销q远大于 memcached set 的开销Q优?set Ҏ升系l整体性能没意义?/p>
知道用 copy-ctor/assign operator ?C++ E序员的试金矟뀂在看到一个开源项目时Q我一般会先查看其 RAII handle class 是否用?copy-ctor/assign operatorQ例?Thread、Mutex、CondVar、ConnectionQ,如果没有Q对其第一印象很差了?/p>
关于 class 命名风格QGoogle、LLVM、Mozilla、muduo 都采?Pascal 风格QLikeThisQ,例如 EventLoop、SudokuSolver {等。正巧它们也都是?2 格羃q的Q可以用 clang-format 自动格式化代码?/p>
Z说说我不认同的两?C++ 教条Q?. 用nullptr替换NULLQ?. 用cstdio头文件替换stdio.h?/font>
因ؓ例如 gettimeofday(&tv, NULL) q种pȝ函数传个 nullptr q去实在是违和,现在?NULL 也能辑ֈ nullptr 的好处,大不了在某个头文仉define一下就行。这条将来或怼变?/p>
另外 ctime 头文件没定义 std::gmtime_rQ?time.h 定义?::gmtime_r。我可不惛_背哪些函数是 C 语言的哪些是 Posix 的,哪些头文件是 C 语言的哪些是 Posix 的(在Linux下,二者基本不分家Q。ؓ了用几个pȝ函数Q例?fcntl() Q,我该 include cfcntl q是 fcntl.hQ用U程?cpthread q是 pthread.hQ我LC?memset() 的参数顺序,因此一般用 bzero() 代替Q但?manpage ?bzero() 声明?strings.hQ那我要不要考虑试试 cstrings 呢?何必l自己找ȝQC++ 标准库之外的内容q脆l一?.h 头文件好了?/p>
性能优化Q?/font>
有些人常常把“性能”挂在嘴边Q而且其以“提高性能”为理q“优化措施”往往不到点子上,只增加了复杂性和l护隑ֺQ降低了代码质量。这属于决策Ҏ偏了。我发现初学者往往q分x微观Q语句Q性能Q比方说兛_ while(true) ?for(;;) 哪个更快Q?+i ?i=i+1 哪个更快Qi/=16 ?i >>= 4 哪个快等{,而忽视了C~译器的优化能力?/p>
有的性能优化Q一是拿不出具体的合理的性能目标Q只惌快越好,二是不能实际准确量验证性能数据Q凭感觉和过时经验行事。在~码的时候,遇到两种做法都可行,决策办法是凭感觉猜?#8220;性能会更?#8221;的一U,而忽视了其他更重要的因素。可L和性能的典型关pd下图Q有多少场合是值得Z性能而牺牲代码的可读性和可维护性呢Q我希望自己的代码位于第 3 区,而一些h以ؓ自己的代码是在第 4 区,其实是在W?1 区?/p>
能在W?4 区写代码的h属于凤毛麟角Q有时候你费劲优化了半天,l果新CPU加了几条指oQ直接在g层面把问题解决了。现在一些h动不动就要挽赯子自己写内存池,L能提高性能Q真?Ulrich Drepper 是水货?Q书W?12.2.8 ?#8220;有必要自行定制内存分配器?#8221;Q你打算如何试内存分配器(mallocQ的性能Q有哪些指标Q有哪些影响因素需要控制或模拟Q比如线E数Q?你的试l果是否反映实际场景Q?/p>
杂项
有h问ؓ什么我?#8220;poco不是服务端C++|络?#8221;Q?http://www.oschina.net/question/12_120943 Q,虽然它也提供了reactorQ因为它的reactor用的?Socket::select()Q虽然后者包装了epollQ但看其实现q道,它每ơ调用都会创建ƈ销?epoll fdQ然后重建整个watch listQ没有哪个服务端|络库会q么做?br />
以下谈一谈这本书的写作背景与内容取舍的原因?
参加工作以来Q我~写q维护了若干C++/Java多线E网l服务程序,q本书ȝ了我在开发维护这cL务程序方面的l验。工作中Q我没有写过单线E的|络服务E序Q没有写qC语言的网l服务程序,也没有写q运行在Windows下的|络服务E序Q因此本书不涉及q些内容?
?#8220;Linux服务端开?#8221;q一背景下,q本书主要讲三个斚w的内?a href="#_ftn1_3988" name="_ftnref1_3988">[1]Q现代C++、多U程、网l编E,分别对应书的W???部分。这不是一本入门书Q本书的读者应该在以上三方面已l具备相当的基础[2]Q网l编E方面,能轻松读?.1节的两个PythonE序QC++斚wQ对12.8节的代码不感到陌生;多线E方面,能明白第1章要解决什么问题?
W??#8220;分布式系l工E实?#8221;详细介绍了这本书的应用背景,卛_发公司内部的分布式服务系l,书中的很多决{(design decisionQ和技术取舍(trade-offQ是在这一应用场景下做出的。以下是各章直接的交叉引用关pdQ没有计引用次敎ͼQ其中第0章是前言Q字母章节是附录。可见第9章是被引用最多的一章,某种意义上可以说W?章既是本书的先决条gQ又是本书的l极目标。由于章节之间存在众多的交叉引用Q去掉Q何一章都会破坏内容的完整性?
q本书的书名原本打算?#8220;Linux C++ 多线E系l编E?#8221;。写完之后发玎ͼ与其他Unix/Linuxpȝ~程斚w的书不同Q这本书有明的应用场景Q因此可以砍掉很多内容,H出重点。甚臛_以说我主要讲别的书没有讲到的内容。这不是一本面面俱到的书,因此最l的书名也就不叫“pȝ~程”了?
同时Q我认ؓ很多教科书上介绍的一些做法是q时的(signalQ,一些是不推荐用的Q从外部l止U程、TCP OOB数据Q,一些是大多数情况下没必要用的Q内存池、lock-free ~程Q。作为全面的教材和手册,把这些内Ҏq去可以理解。但是这本书定位是经验ȝQ我略去了教U书上那些基本用不到的知识点Q以免喧宑֤主,也徏议读者不要把_֊花在那些ơ要问题上?
“信息”按照香农的定义,?#8220;减少不确定?#8221;Q这本书包含的信息正是减选用~程设施QfacilitiesQ方面的不确定性,让读者集中精力攻克本质问题。这本书介绍的方法不一定对于每个应用场景都是最好的Q但肯定是简便易行的Q是旉成本、功能、性能的一U合理折中?
[1] q本书前a的第一句话“本书主要讲述采用C C++ ?x86-64 Linux 上编写多U程 TCP |络服务E序的主常规技?#8221;Q封面印着“C在多核时代采用现?C++ ~写多线E?TCP |络服务器的正规做法”?
[2] 前言写到Q读者应该已l大致读q《现代操作系l》、《UNIX 环境高~程》、《UNIX |络~程》、《C++ Primer》或与之内容相近的书c,熟悉基本概念Qƈ掌握 Pthreads ?Sockets API 的常规用法?
2012-01-28
我在《Linux 多线E服务端~程Q?muduo C++ |络库》第 1.9 ?#8220;再论 shared_ptr 的线E安?#8221;中写道:
Qshared_ptrQ的引用计数本n是安全且无锁的,但对象的d则不是,因ؓ shared_ptr 有两个数据成员,d操作不能原子化?/font>Ҏ文档Q?/font>http://www.boost.org/doc/libs/release/libs/smart_ptr/shared_ptr.htm#ThreadSafetyQ, shared_ptr 的线E安全别和内徏cd、标准库容器、std::string 一P卻I
• 一?shared_ptr 对象实体可被多个U程同时dQ文档例1Q;
• 两个 shared_ptr 对象实体可以被两个线E同时写入(?Q,“析构”写操作Q?/font>
• 如果要从多个U程d同一?shared_ptr 对象Q那么需要加锁(?~5Q?/font>
h意,以上?shared_ptr 对象本n的线E安全别,不是它管理的对象的线E安全别?/font>
后文Qp.18Q则介绍如何高效地加锁解锁。本文则具体分析一下ؓ什?#8220;因ؓ shared_ptr 有两个数据成员,d操作不能原子?/font>”使得多线E读写同一?shared_ptr 对象需要加?/font>。这个在我看来显而易见的l论g也有人抱有疑问,那将DN性的后果。本文以 boost::shared_ptr ZQ与 std::shared_ptr 可能略有区别?/p> shared_ptr 是引用计数型Qreference countingQ智能指针,几乎所有的实现都采用在堆(heapQ上放个计数|countQ的办法Q除此之外理Zq有用@环链表的办法Q不q没有实例)。具体来_shared_ptr<Foo> 包含两个成员Q一个是指向 Foo 的指?ptrQ另一个是 ref_count 指针Q其cd不一定是原始指针Q有可能?class cdQ但不媄响这里的讨论Q,指向堆上?ref_count 对象。ref_count 对象有多个成员,具体的数据结构如?1 所C,其中 deleter ?allocator 是可选的?/p> ?1Qshared_ptr 的数据结构?/p> Z化ƈH出重点Q后文只d use_countQ?/p> 以上?shared_ptr<Foo> x(new Foo); 对应的内存数据结构?/p> 如果再执?shared_ptr<Foo> y = x; 那么对应的数据结构如下?/p> 但是 y=x 涉及两个成员的复Ӟq两步拷贝不会同Ӟ原子Q发生?/p> 中间步骤 1Q复?ptr 指针Q?/p> 中间步骤 2Q复?ref_count 指针Q导致引用计数加 1Q?/p> 步骤1和步?的先后顺序跟实现相关Q因此步?2 里没有画?y.ptr 的指向)Q我见过的都是先1??/p> 既然 y=x 有两个步骤,如果没有 mutex 保护Q那么在多线E里有 race condition?/p> 考虑一个简单的场景Q有 3 ?shared_ptr<Foo> 对象 x、g、nQ?/p> 一开始,各安其事?/p> U程 A 执行 x = g; Q即 read gQ,以下完成了步?1Q还没来及执行步?2。这时切换到?B U程?/p> 同时~程 B 执行 g = n; Q即 write GQ,两个步骤一起完成了?/p> 先是步骤 1Q?/p> 再是步骤 2Q?/p> q是 Foo1 对象已经销毁,x.ptr 成了I悬指针Q?/p> 最后回到线E?AQ完成步?2Q?/p> 多线E无保护地读?gQ造成?#8220;x 是空悬指?#8221;的后果。这正是多线E读写同一?shared_ptr 必须加锁的原因?/p> 当然Qrace condition q不止这一U,其他U程交织QinterweavingQ有可能会造成其他错误?/p> 思考,假如 shared_ptr ?operator= 实现是先复制 ref_countQ步?2Q再复制 ptrQ步?1Q,会有哪些 race conditionQ?/p> 如果?boost::shared_ptr 攑ֈ unordered_set 中,或者用?unordered_map ?keyQ那么要心 hash table 退化ؓ链表?a title="http://stackoverflow.com/questions/6404765/c-shared-ptr-as-unordered-sets-key/12122314#12122314" >http://stackoverflow.com/questions/6404765/c-shared-ptr-as-unordered-sets-key/12122314#12122314 直到 Boost 1.47.0 发布之前Qunordered_set<std::shared_ptr<T> > 虽然可以~译通过Q但是其 hash_value ?shared_ptr 隐式转换?bool 的结果。也是_如果不自定义hash函数Q那?unordered_{set/map} 会退化ؓ链表?a title="https://svn.boost.org/trac/boost/ticket/5216" >https://svn.boost.org/trac/boost/ticket/5216 Boost 1.51 ?boost/functional/hash/extensions.hpp 中增加了有关重蝲Q现在只要包含这个头文gp安全高效C?unordered_set<std::shared_ptr> 了?/p> q也?muduo ?examples/idleconnection CZ要自己定?hash_value(const boost::shared_ptr<T>& x) 函数的原因(书第 7.10.2 节,p.255Q。因?Debian 6 Squeeze、Ubuntu 10.04 LTS 里的 boost 版本都有q个 bug?/p> shared_ptr<Foo> sp(new Foo) 在构?sp 的时候捕获了 Foo 的析构行为。实际上 shared_ptr.ptr ?ref_count.ptr 可以是不同的cdQ只要它们之间存在隐式{换)Q这?shared_ptr 的一大功能。分 3 Ҏ_ 1. 无需虚析构;假设 Bar ?Foo 的基c,但是 Bar ?Foo 都没有虚析构?/p> shared_ptr<Foo> sp1(new Foo); // ref_count.ptr 的类型是 Foo* shared_ptr<Bar> sp2 = sp1; // 可以赋|自动向上转型Qup-castQ?/p> sp1.reset(); // q时 Foo 对象的引用计数降?1 此后 sp2 仍然能安全地理 Foo 对象的生命期Qƈ安全完整地释?FooQ因为其 ref_count C?Foo 的实际类型?/p> 2. shared_ptr<void> 可以指向q安全地理Q析构或防止析构QQ何对象;muduo::net::Channel class ?tie() 函数׃用了q一Ҏ,防止对象q早析构Q见?7.15.3 节?/p> shared_ptr<Foo> sp1(new Foo); // ref_count.ptr 的类型是 Foo* shared_ptr<void> sp2 = sp1; // 可以赋|Foo* ?void* 自动转型 sp1.reset(); // q时 Foo 对象的引用计数降?1 此后 sp2 仍然能安全地理 Foo 对象的生命期Qƈ安全完整地释?FooQ不会出?delete void* 的情况,因ؓ delete 的是 ref_count.ptrQ不?sp2.ptr?/p> 3. 多ѝ?/strong>假设 Bar ?Foo 的多个基cM一Q那么: shared_ptr<Foo> sp1(new Foo); shared_ptr<Bar> sp2 = sp1; // q时 sp1.ptr ?sp2.ptr 可能指向不同的地址Q因?Bar subobject ?Foo object 中的 offset 可能不ؓ0?/p> sp1.reset(); // 此时 Foo 对象的引用计数降?1 但是 sp2 仍然能安全地理 Foo 对象的生命期Qƈ安全完整地释?FooQ因?delete 的不?Bar*Q而是原来?Foo*。换句话_sp2.ptr ?ref_count.ptr 可能h不同的|当然它们的类型也不同Q?/p> Z节省一ơ内存分配,原来 shared_ptr<Foo> x(new Foo); 需要ؓ Foo ?ref_count 各分配一ơ内存,现在?make_shared() 的话Q可以一ơ分配一块够大的内存,?Foo ?ref_count 对象容n。数据结构是Q?/p> 不过 Foo 的构造函数参数要传给 make_shared()Q后者再传给 Foo::Foo()Q这只有?C++11 里通过 perfect forwarding 才能完美解决?/p> (.?)shared_ptr 的数据结?/h1>
多线E无保护d shared_ptr 可能出现?race condition
杂项
shared_ptr 作ؓ unordered_map ?key
Z么图 1 中的 ref_count 也有指向 Foo 的指针?
Z么要量使用 make_shared()Q?/h2>
]]>