<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>STL | 酷 壳 - CoolShell</title>
	<atom:link href="https://coolshell.cn/tag/stl/feed" rel="self" type="application/rss+xml" />
	<link>https://coolshell.cn</link>
	<description>享受编程和技术所带来的快乐 - Coding Your Ambition</description>
	<lastBuildDate>Thu, 04 Dec 2014 02:56:21 +0000</lastBuildDate>
	<language>zh-CN</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=6.2</generator>
	<item>
		<title>面向对象是个骗局？！</title>
		<link>https://coolshell.cn/articles/3036.html</link>
					<comments>https://coolshell.cn/articles/3036.html#comments</comments>
		
		<dc:creator><![CDATA[陈皓]]></dc:creator>
		<pubDate>Wed, 29 Sep 2010 00:37:54 +0000</pubDate>
				<category><![CDATA[C/C++语言]]></category>
		<category><![CDATA[技术读物]]></category>
		<category><![CDATA[编程语言]]></category>
		<category><![CDATA[轶事趣闻]]></category>
		<category><![CDATA[Alexander Stepanov]]></category>
		<category><![CDATA[Bjarne Stroustrup]]></category>
		<category><![CDATA[C++]]></category>
		<category><![CDATA[Object-Oriented]]></category>
		<category><![CDATA[OOP]]></category>
		<category><![CDATA[STL]]></category>
		<category><![CDATA[面向对象]]></category>
		<guid isPermaLink="false">http://coolshell.cn/?p=3036</guid>

					<description><![CDATA[<p>今天在网上看到网页叫“Object Orientation Isa Hoax”——面向对象是一个骗局，标题很有煽动性（注：该网站上还有一个网页叫Object O...</p>
<p class="read-more"><a class="btn btn-default" href="https://coolshell.cn/articles/3036.html"> Read More<span class="screen-reader-text">  Read More</span></a></p>
The post <a href="https://coolshell.cn/articles/3036.html">面向对象是个骗局？！</a> first appeared on <a href="https://coolshell.cn">酷 壳 - CoolShell</a>.]]></description>
										<content:encoded><![CDATA[<p><script async src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=ca-pub-3415450859608158"
     crossorigin="anonymous"></script>今天在网上看到网页叫“<a href="http://c2.com/cgi/wiki?ObjectOrientationIsaHoax" target="_blank">Object Orientation Isa Hoax</a>”——面向对象是一个骗局，标题很有煽动性（注：该网站上还有一个网页叫<a href="http://c2.com/cgi/wiki?ObjectOrientationIsDead" target="_blank">Object Orientation Is Dead</a>），好吧，打开看看上面有些 什么，发现这个网页是在收集一些关于“面向对象的反动言论”，没想到的是，很多言论出自很多大师之口。比如：Alexander Stepanov和Bjarne Stroustrup。这些言论挺有意思的，所以，我摘两段在下面：</p>
<p>第一段是Alexander Stepanov的（不要告诉我你不知道这个人，STL之父，关于他的故事，可以到<a href="http://www.techcn.com.cn/index.php?doc-view-131345.html" target="_blank">这里看看</a>）。他N年前作过一段采访，<a href="http://www.stlport.org/resources/StepanovUSA.html" target="_blank">原文在这里</a>（我非常建议大家去读一下这篇采访，相当过瘾），<a href="http://dev.csdn.net/htmls/11/11440.html" target="_blank">译文在这里</a>（不过有地方把原意都译反了，我重译了一下），其中有一个问答被上述的那个面向对象反动言论的网页收录了：</p>
<figure style="width: 225px" class="wp-caption alignright"><img decoding="async" loading="lazy" title="Alexander Stepanov" src="http://www.techcn.com.cn/uploads/200906/s_1244557971yFeOfA84.jpg" alt="" width="225" height="300" /><figcaption class="wp-caption-text">Alexander Stepanov</figcaption></figure>
<blockquote><p><strong>Question</strong>:<br />
I think STL and Generic Programming mark a definite departure from the common C++ programming style, which I find is almost completely derived from SmallTalk. Do you agree?</p>
<p><strong>提问</strong>：<br />
我认为STL和泛型编程标志着非同一般的C++编程风格，而一般C++风格几乎完全是从SmallTalk派生过来的。你同意吗？</p>
<p><strong>Answer</strong>:<br />
Yes. STL is not object oriented. I think that object orientedness is almost as much of a hoax as Artificial Intelligence. I have yet to see an interesting piece of code that comes from these OO people. In a sense, I am unfair to AI: I learned a lot of stuff from the MIT AI Lab crowd, they have done some really fundamental work: Bill Gosper&#8217;s Hakmem is one of the best things for a programmer to read. AI might not have had a serious foundation, but it produced Gosper and Stallman (Emacs), Moses (Macsyma) and Sussman (Scheme, together with Guy Steele). I find OOP technically unsound. It attempts to decompose the world in terms of interfaces that vary on a single type. To deal with the real problems you need multisorted algebras &#8211; families of interfaces that span multiple types. I find OOP philosophically unsound. It claims that everything is an object. Even if it is true it is not very interesting &#8211; saying that everything is an object is saying nothing at all. I find OOP methodologically wrong. It starts with classes. It is as if mathematicians would start with axioms. You do not start with axioms &#8211; you start with proofs. Only when you have found a bunch of related proofs, can you come up with axioms. You end with axioms. The same thing is true in programming: you have to start with interesting algorithms. Only when you understand them well, can you come up with an interface that will let them work.</p>
<p><strong>回答：</strong><br />
是的。STL不是面向对象的。我认为面向对象和人工智能差不多，都是个骗局。我至今仍然没有从那些OO编程的人那里看到一丁点有意思的代码。从某种意义上来说，我这么说对人工智能（AI）并不公平：因为我听说过很多MIT（麻省理工大） AI实验室里一帮人搞出来的东西，而且他们的确直正干了一些基础性的工作：Bill Gosper的Hakmem是程序员最好的读物之一。AI或许没有一个实实在在的基础，但它造就了Gosper和Stallman（Emacs）， Moses（Macsyma）和Sussman（Scheme， 和Guy Steele一起）。</p>
<ul>
<li>我发现OOP在技术上是荒谬的，它企图把事物按照不同单个类型的接口来解构，为了处理实际问题，你需要多种代数方法——横跨多种类型的接口族；</li>
<li>我发现OOP在哲学上是荒谬的，它声称一切都是对象。即使这是真的也不是很有趣——因为说一切都是对象跟什么都没说一样；</li>
<li>我发现OOP的方法论是错误的，它从类开始，就好像数学应该从从公理开始一样。其实你不会是从公理开始的，而是从证明开始。直到你找到了一大堆相关证据后你才能归纳出公理，然后以公理结束。在程序设计方面存在着同样的事实：你要从有趣的算法开始。只有很好地理解了算法，你才有可能提炼出接口以让其工作。</li>
</ul>
</blockquote>
<p><span style="color: #ffffff;">&lt;&#8212;&#8212;&#8212;&gt;</span></p>
<p>下面，我们再来看C++的发明者Bjarne Stroustrup，在1998年IEEE采访时的一段话（<a href="http://www2.research.att.com/~bs/ieee_interview.html" target="_blank">全篇见这里</a>），下面是其中的几段话：（我的翻译如下）</p>
<p><span id="more-3036"></span></p>
<figure style="width: 200px" class="wp-caption alignright"><img decoding="async" loading="lazy" title="Bjarne Stroustrup" src="http://www.techcn.com.cn/uploads/200906/1244559516ywHaeEXL.png" alt="" width="200" height="245" /><figcaption class="wp-caption-text">Bjarne Stroustrup</figcaption></figure>
<blockquote><p>So what is OO? Certainly not every good program is object-oriented, and not every object-oriented program is good. If this were so, &#8220;object-oriented&#8221; would simply be a synonym for &#8220;good,&#8221; and the concept would be a vacuous buzzword of little help when you need to make practical decisions. I tend to equate OOP with heavy use of class hierarchies and virtual functions (called methods in some languages). This definition is historically accurate because class hierarchies and virtual functions together with their accompanying design philosophy were what distinguished Simula from the other languages of its time. In fact, it is this aspect of Simula&#8217;s legacy that Smalltalk has most heavily emphasized.</p>
<p>那么，什么是OO面向对象？当然，不会是所有的程序都是面向对象的，而且，也不是所有的面向对象程序就是好的。如果面向对象是好的，那么“Object-Oriented”应该成为“Good”的同义词，并且，OO概念只会成为一个假大空的口号，在你需要做出实际决定时只可能帮你那么一丁点。我倾向于把OOP等价于大量使用继承类和虚函数（某些语言的调用方法）。从历史上来说，这个定义是精确的，因为，在那个时候，只有类的继承和虚函数一起存在的设计哲学，才能把Simula和其它语言分别开来。事实上，Smalltalk相当地强调着这种Simula的遗留问题。</p>
<p>Defining OO as based on the use of class hierarchies and virtual functions is also practical in that it provides some guidance as to where OO is likely to be successful. You look for concepts that have a hierarchical ordering, for variants of a concept that can share an implementation, and for objects that can be manipulated through a common interface without being of exactly the same type. Given a few examples and a bit of experience, this can be the basis for a very powerful approach to design.</p>
<p>用继承类和虚函数来定义OO在实际上可以让很多OO指导性的东西更能成功一些。在解决问题时，寻找的那些有层级次序的对象，以应对不同对象也可以重用同一个实现，并且对象可以被某个共同的接口来操作而不需要完全相同的类型。在你了解了一些示例和拥有了一些经验后，OO可以成为Design的一个强有力的基础。</p>
<p>However, not every concept naturally and usefully fits into a hierarchy, not every relationship among concepts is hierarchical, and not every problem is best approached with a primary focus on objects. For example, some problems really are primarily algorithmic. Consequently, a general-purpose programming language should support a variety of ways of thinking and a variety of programming styles. This variety results from the diversity of problems to be solved and the many ways of solving them. C++ supports a variety of programming styles and is therefore more appropriately called a multiparadigm, rather than an object-oriented, language (assuming you need a fancy label).</p>
<p>然而，并不是每一个对象都自然地有效地适合继承，并不是每一个对象间的关系都是继承，也并不是每一个问题的最佳解决途径需要主要地通过对象。例如，很多问题主要是算法问题（译注：如业务逻辑，数据流等）。我们知道，一个一般性的编程语言都应该有能力支持不同的思路和不同的编程风格。这样，对于问题的多样性，我们可以使用许许多多不同的的方法去解决他们，这就产生了很多的不同解法。C++支持编程风格的多样性，因此，C++叫做“多范式  multi-paradigm”会更合适一些，而不是一个面向对象语言。</p></blockquote>
<p><span style="color: #ffffff;">&lt;&#8212;&#8212;&#8212;&gt;</span></p>
<p>我个人在看过这些言论后，我先不管“面向对象是不是一个骗局”，不过从某种角度上来看的确是有些问题的，C++、OO、XML、SOA、网格计算等等诸如此类的东西的确被挂上了神圣的光坏。这些东西出来的时候总是只有一种赞美的声音。无论好坏，只有一种声音总是令人恐怖的，无论好坏，有不同的声音总是好的，每当这个社会或是我们的IT界大张旗鼓地鼓吹或是信仰某些东西，却没有任何一点不同意见的时候，我就会感到一种莫名的恐慌。我知道，这是我们从小受到的那种“非黑即白”的价值观教育所致，事物要么全是好的，要么全是不好的。其实任何事物都是有好有不好的，C++，敏捷开发，CMMi，OO，设计模式，重构，等等等等，他们都有好的也有不好的，关键看你怎么来使用（如之前的《<a title="代码重构的一个示例" href="https://coolshell.cn/articles/3005.html" target="_blank">代码重构的一个示例</a>》）。这个世界只有适合不适合的东西，不会出现放之四海皆准的东西，也不可能出现一种可以解决所有问题的东西，如果有，那么这种东西必然是一种宗教性质的用来洗脑的东西。</p>
<p>所以，每当在我身边看到或听到那些只有一种声音有如“电视购物”或是“新闻联播”之类的宣传或是鼓动的时候，我就感到很一种莫名的反感…… 不多说了，还是交给大家来评价吧。我仅以此篇文章献给那些OO-Oriented，Design Pattern-Oriented，Agile-Oriented，Process-Oriented，等等有着宗教信仰一般的人和事。<!--



<p align="center"><a href= target=_blank><img decoding="async" src=""></a></p>





<p align="center"><img decoding="async" src="https://coolshell.cn/wp-content/uploads/2020/03/coolshell.weixin.jpg"> <img decoding="async" loading="lazy" src="https://coolshell.cn/wp-content/uploads/2020/03/coolshell.mini_.jpg" width="300" height="300"> <br />关注CoolShell微信公众账号和微信小程序</p>

 

--></p>
<div style="margin-top: 15px; font-size: 16px;color: #cc0000;">
<p align="center"><strong>（转载本站文章请注明作者和出处 <a href="https://coolshell.cn/">酷 壳 &#8211; CoolShell</a> ，请勿用于任何商业用途）</strong></p>
</div>

<div class="wp_rp_wrap  wp_rp_vertical_m" id="wp_rp_first"><div class="wp_rp_content"><h3 class="related_post_title">相关文章</h3><ul class="related_post wp_rp"><li ><a href="https://coolshell.cn/articles/8745.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/plugins/wordpress-23-related-posts-plugin/static/thumbs/8.jpg" alt="如此理解面向对象编程" width="150" height="150" /></a><a href="https://coolshell.cn/articles/8745.html" class="wp_rp_title">如此理解面向对象编程</a></li><li ><a href="https://coolshell.cn/articles/5202.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/plugins/wordpress-23-related-posts-plugin/static/thumbs/14.jpg" alt="对象的消息模型" width="150" height="150" /></a><a href="https://coolshell.cn/articles/5202.html" class="wp_rp_title">对象的消息模型</a></li><li ><a href="https://coolshell.cn/articles/4535.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/plugins/wordpress-23-related-posts-plugin/static/thumbs/1.jpg" alt="一些软件设计的原则" width="150" height="150" /></a><a href="https://coolshell.cn/articles/4535.html" class="wp_rp_title">一些软件设计的原则</a></li><li ><a href="https://coolshell.cn/articles/12199.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/plugins/wordpress-23-related-posts-plugin/static/thumbs/22.jpg" alt="C++ STL string的Copy-On-Write技术" width="150" height="150" /></a><a href="https://coolshell.cn/articles/12199.html" class="wp_rp_title">C++ STL string的Copy-On-Write技术</a></li><li ><a href="https://coolshell.cn/articles/20845.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2020/03/rust-social-wide-150x150.jpg" alt="Rust语言的编程范式" width="150" height="150" /></a><a href="https://coolshell.cn/articles/20845.html" class="wp_rp_title">Rust语言的编程范式</a></li><li ><a href="https://coolshell.cn/articles/18360.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2018/05/300x262-150x150.jpg" alt="程序员练级攻略（2018)  与我的专栏" width="150" height="150" /></a><a href="https://coolshell.cn/articles/18360.html" class="wp_rp_title">程序员练级攻略（2018)  与我的专栏</a></li></ul></div></div>The post <a href="https://coolshell.cn/articles/3036.html">面向对象是个骗局？！</a> first appeared on <a href="https://coolshell.cn">酷 壳 - CoolShell</a>.]]></content:encoded>
					
					<wfw:commentRss>https://coolshell.cn/articles/3036.html/feed</wfw:commentRss>
			<slash:comments>79</slash:comments>
		
		
			</item>
		<item>
		<title>C++ STL string的Copy-On-Write技术</title>
		<link>https://coolshell.cn/articles/12199.html</link>
					<comments>https://coolshell.cn/articles/12199.html#comments</comments>
		
		<dc:creator><![CDATA[陈皓]]></dc:creator>
		<pubDate>Wed, 23 Jun 2004 02:36:50 +0000</pubDate>
				<category><![CDATA[C/C++语言]]></category>
		<category><![CDATA[编程语言]]></category>
		<category><![CDATA[C++]]></category>
		<category><![CDATA[STL]]></category>
		<category><![CDATA[String]]></category>
		<guid isPermaLink="false">http://coolshell.cn/?p=12199</guid>

					<description><![CDATA[<p>Scott Meyers在《More Effective C++》中举了个例子，不知你是否还记得？在你还在上学的时候，你的父母要你不要看电视，而去复习功课，于是...</p>
<p class="read-more"><a class="btn btn-default" href="https://coolshell.cn/articles/12199.html"> Read More<span class="screen-reader-text">  Read More</span></a></p>
The post <a href="https://coolshell.cn/articles/12199.html">C++ STL string的Copy-On-Write技术</a> first appeared on <a href="https://coolshell.cn">酷 壳 - CoolShell</a>.]]></description>
										<content:encoded><![CDATA[<p><script async src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=ca-pub-3415450859608158"
     crossorigin="anonymous"></script>Scott Meyers在《More Effective C++》中举了个例子，不知你是否还记得？在你还在上学的时候，你的父母要你不要看电视，而去复习功课，于是你把自己关在房间里，做出一副正在复习功课的样子，其实你在干着别的诸如给班上的某位女生写情书之类的事，而一旦你的父母出来在你房间要检查你是否在复习时，你才真正捡起课本看书。这就是“拖延战术”，直到你非要做的时候才去做。</p>
<p>当然，这种事情在现实生活中时往往会出事，但其在编程世界中摇身一变，就成为了最有用的技术，正如C++中的可以随处声明变量的特点一样，Scott Meyers推荐我们，在真正需要一个存储空间时才去声明变量（分配内存），这样会得到程序在运行时最小的内存花销。执行到那才会去做分配内存这种比较耗时的工作，这会给我们的程序在运行时有比较好的性能。必竟，20%的程序运行了80%的时间。</p>
<p>当然，拖延战术还并不只是这样一种类型，这种技术被我们广泛地应用着，特别是在操作系统当中，当一个程序运行结束时，操作系统并不会急着把其清除出内存，原因是有可能程序还会马上再运行一次（从磁盘把程序装入到内存是个很慢的过程），而只有当内存不够用了，才会把这些还驻留内存的程序清出。</p>
<p>写时才拷贝（Copy-On-Write）技术，就是编程界“懒惰行为”——拖延战术的产物。举个例子，比如我们有个程序要写文件，不断地根据网络传来的数据写，如果每一次fwrite或是fprintf都要进行一个磁盘的I/O操作的话，都简直就是性能上巨大的损失，因此通常的做法是，每次写文件操作都写在特定大小的一块内存中（磁盘缓存），只有当我们关闭文件时，才写到磁盘上（这就是为什么如果文件不关闭，所写的东西会丢失的原因）。更有甚者是文件关闭时都不写磁盘，而一直等到关机或是内存不够时才写磁盘，Unix就是这样一个系统，如果非正常退出，那么数据就会丢失，文件就会损坏。</p>
<p>呵呵，为了性能我们需要冒这样大的风险，还好我们的程序是不会忙得忘了还有一块数据需要写到磁盘上的，所以这种做法，还是很有必要的。</p>
<p><span id="more-12199"></span></p>
<h4>STL类std::string的Copy-On-Write</h4>
<p>在我们经常使用的STL标准模板库中的string类，也是一个具有写时才拷贝技术的类。C++曾在性能问题上被广泛地质疑和指责过，为了提高性能，STL中的许多类都采用了Copy-On-Write技术。这种偷懒的行为的确使使用STL的程序有着比较高要性能。</p>
<p>这里，我想从C++类或是设计模式的角度为各位揭开Copy-On-Write技术在string中实现的面纱，以供各位在用C++进行类库设计时做一点参考。</p>
<p>在讲述这项技术之前，我想简单地说明一下string类内存分配的概念。通过常，string类中必有一个私有成员，其是一个char*，用户记录从堆上分配内存的地址，其在构造时分配内存，在析构时释放内存。因为是从堆上分配内存，所以string类在维护这块内存上是格外小心的，string类在返回这块内存地址时，只返回const char*，也就是只读的，如果你要写，你只能通过string提供的方法进行数据的改写。</p>
<h4>特性</h4>
<p>由表及里，由感性到理性，我们先来看一看string类的Copy-On-Write的表面特征。让我们写下下面的一段程序：</p>
<pre data-enlighter-language="c" class="EnlighterJSRAW">#include &lt;stdio.h&gt;
#include &lt;string&gt;
using namespace std;
 
main()
{
    string str1 = &quot;hello world&quot;;
    string str2 = str1;

    printf (&quot;Sharing the memory:/n&quot;);
    printf (&quot;/tstr1&#039;s address: %x/n&quot;, str1.c_str() );
    printf (&quot;/tstr2&#039;s address: %x/n&quot;, str2.c_str() );

    str1[1]=&#039;q&#039;;
    str2[1]=&#039;w&#039;;

    printf (&quot;After Copy-On-Write:/n&quot;);
    printf (&quot;/tstr1&#039;s address: %x/n&quot;, str1.c_str() );
    printf (&quot;/tstr2&#039;s address: %x/n&quot;, str2.c_str() );

    return 0;
}</pre>
<p>这个程序的意图就是让第二个<span lang="EN">string</span>通过第一个<span lang="EN">string</span>构造，然后打印出其存放数据的内存地址，然后分别修改<span lang="EN">str1</span>和<span lang="EN">str2</span>的内容，再查一下其存放内存的地址。程序的输出是这样的（我在<span lang="EN">VC6.0</span>和<span lang="EN">g++ 2.95</span>都得到了同样的结果）：</p>
<pre data-enlighter-language="shell" class="EnlighterJSRAW">&gt; g++ -o stringTest stringTest.cpp
&gt; ./stringTest
Sharing the memory:
        str1&#039;s address: 343be9
        str2&#039;s address: 343be9
After Copy-On-Write:
        str1&#039;s address: 3407a9
        str2&#039;s address: 343be9</pre>
<p>从结果中我们可以看到，在开始的两个语句后，<span lang="EN">str1</span>和<span lang="EN">str2</span>存放数据的地址是一样的，而在修改内容后，<span lang="EN">str1</span>的地址发生了变化，而<span lang="EN">str2</span>的地址还是原来的。从这个例子，我们可以看到<span lang="EN">string</span>类的<span lang="EN">Copy-On-Write</span>技术。</p>
<h4> 深入</h4>
<p>在深入这前，通过上述的演示，我们应该知道在string类中，要实现写时才拷贝，需要解决两个问题，一个是内存共享，一个是Copy-On-Wirte，这两个主题会让我们产生许多疑问，还是让我们带着这样几个问题来学习吧：</p>
<p style="padding-left: 30px;">1、  Copy-On-Write的原理是什么？</p>
<p style="padding-left: 30px;">2、  string类在什么情况下才共享内存的？</p>
<p style="padding-left: 30px;">3、  string类在什么情况下触发写时才拷贝（Copy-On-Write）?</p>
<p style="padding-left: 30px;">4、  Copy-On-Write时，发生了什么？</p>
<p style="padding-left: 30px;">5、  Copy-On-Write的具体实现是怎么样的？</p>
<p>喔，你说只要看一看STL中stirng的源码你就可以找到答案了。当然，当然，我也是参考了string的父模板类basic_string的源码。但是，如果你感到看STL的源码就好像看机器码，并严重打击你对C++自信心，乃至产生了自己是否懂C++的疑问，如果你有这样的感觉，那么还是继续往下看我的这篇文章吧。</p>
<p>OK，让我们一个问题一个问题地探讨吧，慢慢地所有的技术细节都会浮出水面的。</p>
<p>&nbsp;</p>
<h4>Copy-On-Write的原理是什么？</h4>
<p>有一定经验的程序员一定知道，Copy-On-Write一定使用了“引用计数”，是的，必然有一个变量类似于RefCnt。当第一个类构造时，string的构造函数会根据传入的参数从堆上分配内存，当有其它类需要这块内存时，这个计数为自动累加，当有类析构时，这个计数会减一，直到最后一个类析构时，此时的RefCnt为1或是0，此时，程序才会真正的Free这块从堆上分配的内存。</p>
<p>是的，<strong>引用计数就是string类中写时才拷贝的原理</strong>！</p>
<p>不过，问题又来了，这个RefCnt该存在在哪里呢？如果存放在string类中，那么每个string的实例都有各自的一套，根本不能共有一个RefCnt，如果是声明成全局变量，或是静态成员，那就是所有的string类共享一个了，这也不行，我们需要的是一个“民主和集中”的一个解决方法。这是如何做到的呢？呵呵，人生就是一个糊涂后去探知，知道后和又糊涂的循环过程。别急别急，在后面我会给你一一道来的。</p>
<p>&nbsp;</p>
<h4>string类在什么情况下才共享内存的？</h4>
<p>&nbsp;</p>
<p>这个问题的答案应该是明显的，根据常理和逻辑，如果一个类要用另一个类的数据，那就可以共享被使用类的内存了。这是很合理的，如果你不用我的，那就不用共享，只有你使用我的，才发生共享。</p>
<p>&nbsp;</p>
<p>使用别的类的数据时，无非有两种情况，1）以别的类构造自己，2）以别的类赋值。第一种情况时会触发拷贝构造函数，第二种情况会触发赋值操作符。这两种情况我们都可以在类中实现其对应的方法。对于第一种情况，只需要在string类的拷贝构造函数中做点处理，让其引用计数累加；同样，对于第二种情况，只需要重载string类的赋值操作符，同样在其中加上一点处理。</p>
<p>唠叨几句：</p>
<p><strong>1）构造和赋值的差别</strong></p>
<p>对于前面那个例程中的这两句：</p>
<pre data-enlighter-language="c" class="EnlighterJSRAW">string str1 = &quot;hello world&quot;;

string str2 = str1;</pre>
<p>不要以为有“=”就是赋值操作，其实，这两条语句等价于：</p>
<pre data-enlighter-language="c" class="EnlighterJSRAW">string str1 (&quot;hello world&quot;);   //调用的是构造函数

string str2 (str1);            //调用的是拷贝构造函数</pre>
<p>如果str2是下面的这样情况：</p>
<pre data-enlighter-language="c" class="EnlighterJSRAW">string str2;      //调用参数默认为空串的构造函数：string str2(“”);

str2 = str1;     //调用str2的赋值操作：str2.operator=(str1);</pre>
<p><strong>2) 另一种情况</strong></p>
<pre data-enlighter-language="c" class="EnlighterJSRAW">char tmp[]=”hello world”;

string str1 = tmp;

string str2 = tmp;</pre>
<p>这种情况下会触发内存的共享吗？想当然的，应该要共享。可是根据我们前面所说的共享内存的情况，两个string类的声明和初始语句并不符合我前述的两种情况，所以其并不发生内存共享。而且，C++现有特性也无法让我们做到对这种情况进行类的内存共享。</p>
<h4>string类在什么情况下触发写时才拷贝（Copy-On-Write）?</h4>
<p>哦，什么时候会发现写时才拷贝？很显然，当然是在共享同一块内存的类发生内容改变时，才会发生Copy-On-Write。比如string类的[]、=、+=、+、操作符赋值，还有一些string类中诸如insert、replace、append等成员函数,包括类的析构时。</p>
<p>修改数据才会触发Copy-On-Write，不修改当然就不会改啦。这就是托延战术的真谛，非到要做的时候才去做。</p>
<h4>Copy-On-Write时，发生了什么？</h4>
<p>我们可能根据那个访问计数来决定是否需要拷贝，参看下面的代码：</p>
<pre data-enlighter-language="c" class="EnlighterJSRAW">If  ( RefCnt&gt;0 ) {
    char* tmp =  (char*) malloc(strlen(_Ptr)+1);
    strcpy(tmp, _Ptr);
    _Ptr = tmp;
}</pre>
<p>&nbsp;</p>
<p>上面的代码是一个假想的拷贝方法，如果有别的类在引用（检查引用计数来获知）这块内存，那么就需要把更改类进行“拷贝”这个动作。</p>
<p>我们可以把这个拷的运行封装成一个函数，供那些改变内容的成员函数使用。</p>
<h4>Copy-On-Write的具体实现是怎么样的？</h4>
<p>最后的这个问题，我们主要解决的是那个“民主集中”的难题。请先看下面的代码：</p>
<pre data-enlighter-language="c" class="EnlighterJSRAW">string h1 = “hello”;
string h2= h1;
string h3;
h3 = h2;
 
string w1 = “world”;
string w2(“”);
w2=w1;</pre>
<p>很明显，我们要让h1、h2、h3共享同一块内存，让w1、w2共享同一块内存。因为，在h1、h2、h3中，我们要维护一个引用计数，在w1、w2中我们又要维护一个引用计数。</p>
<p>如何使用一个巧妙的方法产生这两个引用计数呢？我们想到了string类的内存是在堆上动态分配的，既然共享内存的各个类指向的是同一个内存区，我们为什么不在这块区上多分配一点空间来存放这个引用计数呢？这样一来，所有共享一块内存区的类都有同样的一个引用计数，而这个变量的地址既然是在共享区上的，那么所有共享这块内存的类都可以访问到，也就知道这块内存的引用者有多少了。</p>
<p>请看下图：</p>
<p><img decoding="async" loading="lazy" class="aligncenter size-full wp-image-12200" src="https://coolshell.cn/wp-content/uploads/2014/12/o_string.jpg" alt="o_string" width="409" height="138" srcset="https://coolshell.cn/wp-content/uploads/2014/12/o_string.jpg 409w, https://coolshell.cn/wp-content/uploads/2014/12/o_string-300x101.jpg 300w" sizes="(max-width: 409px) 100vw, 409px" /></p>
<p>于是，有了这样一个机制，每当我们为<span lang="EN">string</span>分配内存时，我们总是要多分配一个空间用来存放这个引用计数的值，只要发生拷贝构造可是赋值时，这个内存的值就会加一。而在内容修改时，<span lang="EN">string</span>类为查看这个引用计数是否为<span lang="EN">0</span>，如果不为零，表示有人在共享这块内存，那么自己需要先做一份拷贝，然后把引用计数减去一，再把数据拷贝过来。下面的几个程序片段说明了这两个动作：</p>
<pre data-enlighter-language="c" class="EnlighterJSRAW">//构造函数（分存内存）
    string::string(const char* tmp)
{
    _Len = strlen(tmp);
    _Ptr = new char[_Len+1+1];
    strcpy( _Ptr, tmp );
    _Ptr[_Len+1]=0;  // 设置引用计数  
}
 
//拷贝构造（共享内存）
    string::string(const string&amp; str)
    {
         if (*this != str){
              this-&gt;_Ptr = str.c_str();   //共享内存
              this-&gt;_Len = str.szie();
              this-&gt;_Ptr[_Len+1] ++;  //引用计数加一
         }
}
 
//写时才拷贝Copy-On-Write
char&amp; string::operator[](unsigned int idx)
{
    if (idx &gt; _Len || _Ptr == 0 ) {
         static char nullchar = 0;
return nullchar;
          }
   
_Ptr[_Len+1]--;   //引用计数减一
    char* tmp = new char[_Len+1+1];
    strncpy( tmp, _Ptr, _Len+1);
    _Ptr = tmp;
    _Ptr[_Len+1]=0; // 设置新的共享内存的引用计数
   
    return _Ptr[idx];
}

//析构函数的一些处理
~string()
{ 
_Ptr[_Len+1]--;   //引用计数减一
   
         // 引用计数为0时，释放内存 
    if (_Ptr[_Len+1]==0) {
        delete[] _Ptr;
         }
 
}</pre>
<p>哈哈，整个技术细节完全浮出水面。</p>
<p>&nbsp;</p>
<p>不过，这和STL中basic_string的实现细节还有一点点差别，在你打开STL的源码时，你会发现其取引用计数是通过这样的访问：_Ptr[-1]，标准库中，把这个引用计数的内存分配在了前面（我给出来的代码是把引用计数分配以了后面，这很不好），分配在前的好处是当string的长度扩展时，只需要在后面扩展其内存，而不需要移动引用计数的内存存放位置，这又节省了一点时间。</p>
<p>STL中的string的内存结构就像我前面画的那个图一样，_Ptr指着是数据区，而RefCnt则在_Ptr-1 或是_Ptr[-1]处。</p>
<h4>副作用</h4>
<p>是谁说的“有太阳的地方就会有黑暗”？或许我们中的许多人都很迷信标准的东西，认为其是久经考验，不可能出错的。呵呵，千万不要有这种迷信，因为任何设计再好，编码再好的代码在某一特定的情况下都会有Bug，STL同样如此，string类的这个共享内存/写时才拷贝技术也不例外，而且这个Bug或许还会让你的整个程序crash掉！</p>
<p>不信？！那么让我们来看一个测试案例。假设有一个动态链接库（叫myNet.dll或myNet.so）中有这样一个函数返回的是string类：</p>
<pre data-enlighter-language="c" class="EnlighterJSRAW">string GetIPAddress(string hostname)
{
    static string ip;
    ……
    ……
    return ip;
}</pre>
<p>而你的主程序中动态地载入这个动态链接库，并调用其中的这个函数：</p>
<pre data-enlighter-language="c" class="EnlighterJSRAW">main()
{
    //载入动态链接库中的函数
    hDll = LoadLibraray(…..);
    pFun =  GetModule(hDll, “GetIPAddress”);
     
    //调用动态链接库中的函数
    string ip = (*pFun)(“host1”);
    ……
    ……
    //释放动态链接库
    FreeLibrary(hDll);
    ……
    cout &lt;&lt; ip &lt;&lt; endl;
}</pre>
<p>让我们来看看这段代码，程序以动态方式载入动态链接库中的函数，然后以函数指针的方式调用动态链接库中的函数，并把返回值放在一个string类中，然后释放了这个动态链接库。释放后，输入ip的内容。</p>
<p>根据函数的定义，我们知道函数是“值返回”的，所以，函数返回时，一定会调用拷贝构造函数，又根据string类的内存共享机制，在主程序中变量ip是和函数内部的那个静态string变量共享内存（这块内存区是在动态链接库的地址空间的）。而我们假设在整个主程序中都没有对ip的值进行修改过。那么在当主程序释放了动态链接库后，那个共享的内存区也随之释放。所以，以后对ip的访问，必然做造成内存地址访问非法，造成程序crash。即使你在以后没有使用到ip这个变量，那么在主程序退出时也会发生内存访问异常，因为程序退出时，ip会析构，在析构时就会发生内存访问异常。</p>
<p>内存访问异常，意味着两件事：1）无论你的程序再漂亮，都会因为这个错误变得暗淡无光，你的声誉也会因为这个错误受到损失。2）未来的一段时间，你会被这个系统级错误所煎熬（在C++世界中，找到并排除这种内存错误并不是一件容易的事情）。这是C/C++程序员永远的心头之痛，千里之堤，溃于蚁穴。而如果你不清楚string类的这种特征，在成千上万行代码中找这样一个内存异常，简直就是一场噩梦。</p>
<p>备注：要改正上述的Bug，有很多种方法，这里提供一种仅供参考：</p>
<p><code data-enlighter-language="c" class="EnlighterJSRAW">string ip = (*pFun)(“host1”).cstr();</code></p>
<h4>后记</h4>
<p>&nbsp;</p>
<p>文章到这里也应该结束了，这篇文章的主要有以下几个目的：</p>
<p>1）向大家介绍一下写时才拷贝/内存共享这种技术。<br />
2）以STL中的string类为例，向大家介绍了一种设计模式。<br />
3）在C++世界中，无论你的设计怎么精巧，代码怎么稳固，都难以照顾到所有的情况。智能指针更是一个典型的例子，无论你怎么设计，都会有非常严重的BUG。<br />
4）C++是一把双刃剑，只有了解了原理，你才能更好的使用C++。否则，必将引火烧身。如果你在设计和使用类库时有一种“玩C++就像玩火，必须千万小心”的感觉，那么你就入门了，等你能把这股“火”控制的得心应手时，那才是学成了。</p>
<p><strong>更新：在最新的STL中，这个特性已经被去掉了。有一个原因是线程不安全！COW其实还是比较危险的。</strong></p>
<p>（全文完）</p>
<p>&nbsp;<!--



<p align="center"><a href= target=_blank><img decoding="async" src=""></a></p>





<p align="center"><img decoding="async" src="https://coolshell.cn/wp-content/uploads/2020/03/coolshell.weixin.jpg"> <img decoding="async" loading="lazy" src="https://coolshell.cn/wp-content/uploads/2020/03/coolshell.mini_.jpg" width="300" height="300"> <br />关注CoolShell微信公众账号和微信小程序</p>

 

--></p>
<div style="margin-top: 15px; font-size: 16px;color: #cc0000;">
<p align="center"><strong>（转载本站文章请注明作者和出处 <a href="https://coolshell.cn/">酷 壳 &#8211; CoolShell</a> ，请勿用于任何商业用途）</strong></p>
</div>

<div class="wp_rp_wrap  wp_rp_vertical_m" ><div class="wp_rp_content"><h3 class="related_post_title">相关文章</h3><ul class="related_post wp_rp"><li ><a href="https://coolshell.cn/articles/3806.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2011/02/C_String-150x150.jpg" alt="Google图片搜索下的的C String" width="150" height="150" /></a><a href="https://coolshell.cn/articles/3806.html" class="wp_rp_title">Google图片搜索下的的C String</a></li><li ><a href="https://coolshell.cn/articles/3036.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/plugins/wordpress-23-related-posts-plugin/static/thumbs/19.jpg" alt="面向对象是个骗局？！" width="150" height="150" /></a><a href="https://coolshell.cn/articles/3036.html" class="wp_rp_title">面向对象是个骗局？！</a></li><li ><a href="https://coolshell.cn/articles/20845.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2020/03/rust-social-wide-150x150.jpg" alt="Rust语言的编程范式" width="150" height="150" /></a><a href="https://coolshell.cn/articles/20845.html" class="wp_rp_title">Rust语言的编程范式</a></li><li ><a href="https://coolshell.cn/articles/18360.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2018/05/300x262-150x150.jpg" alt="程序员练级攻略（2018)  与我的专栏" width="150" height="150" /></a><a href="https://coolshell.cn/articles/18360.html" class="wp_rp_title">程序员练级攻略（2018)  与我的专栏</a></li><li ><a href="https://coolshell.cn/articles/18024.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2017/07/api-design-300x278-2-150x150.jpg" alt="API设计原则 &#8211; Qt官网的设计实践总结" width="150" height="150" /></a><a href="https://coolshell.cn/articles/18024.html" class="wp_rp_title">API设计原则 &#8211; Qt官网的设计实践总结</a></li><li ><a href="https://coolshell.cn/articles/12052.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/plugins/wordpress-23-related-posts-plugin/static/thumbs/29.jpg" alt="Leetcode 编程训练" width="150" height="150" /></a><a href="https://coolshell.cn/articles/12052.html" class="wp_rp_title">Leetcode 编程训练</a></li></ul></div></div>The post <a href="https://coolshell.cn/articles/12199.html">C++ STL string的Copy-On-Write技术</a> first appeared on <a href="https://coolshell.cn">酷 壳 - CoolShell</a>.]]></content:encoded>
					
					<wfw:commentRss>https://coolshell.cn/articles/12199.html/feed</wfw:commentRss>
			<slash:comments>16</slash:comments>
		
		
			</item>
	</channel>
</rss>
