﻿<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:trackback="http://madskills.com/public/xml/rss/module/trackback/" xmlns:wfw="http://wellformedweb.org/CommentAPI/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/"><channel><title>C++博客-巢穴-随笔分类-c++</title><link>http://www.cppblog.com/ccl0326/category/11336.html</link><description>about:blank</description><language>zh-cn</language><lastBuildDate>Tue, 30 Mar 2010 04:56:01 GMT</lastBuildDate><pubDate>Tue, 30 Mar 2010 04:56:01 GMT</pubDate><ttl>60</ttl><item><title>iocp(我所见过讲的最通俗易懂的……）</title><link>http://www.cppblog.com/ccl0326/archive/2010/03/29/110911.html</link><dc:creator>Vincent</dc:creator><author>Vincent</author><pubDate>Mon, 29 Mar 2010 13:05:00 GMT</pubDate><guid>http://www.cppblog.com/ccl0326/archive/2010/03/29/110911.html</guid><wfw:comment>http://www.cppblog.com/ccl0326/comments/110911.html</wfw:comment><comments>http://www.cppblog.com/ccl0326/archive/2010/03/29/110911.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/ccl0326/comments/commentRss/110911.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/ccl0326/services/trackbacks/110911.html</trackback:ping><description><![CDATA[<p>理解I/O Completion Port <br>摘自:http://gzlyb.cnblogs.com/archive/2005/09/30/247049.aspx <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 欢迎阅读此篇IOCP教程。我将先给出IOCP的定义然后给出它的实现方法，最后剖析一个Echo程序来为您拨开IOCP的谜云，除去你心中对IOCP的烦恼。OK，但我不能保证你明白IOCP的一切，但我会尽我最大的努力。以下是我会在这篇文章中提到的相关技术：<br>　　I/O端口<br>　　同步/异步<br>　　堵塞/非堵塞<br>　　服务端/客户端<br>　　多线程程序设计<br>　　Winsock API 2.0<br>　　在这之前，我曾经开发过一个项目，其中一块需要网络支持，当时还考虑到了代码的可移植性，只要使用select,connect,accept,listen,send还有recv,再加上几个#ifdef的封装以用来处理Winsock和BSD套接字[socket]中间的不兼容性，一个网络子系统只用了几个小时很少的代码就写出来了，至今还让我很回味。那以后很长时间也就没再碰了。<br>　　前些日子，我们策划做一个网络游戏，我主动承担下网络这一块，想想这还不是小case,心里偷着乐啊。网络游戏好啊，网络游戏为成百上千的玩家提供了乐趣和令人着秘的游戏体验，他们在线上互相战斗或是加入队伍去战胜共同的敌人。我信心满满的准备开写我的网络，于是乎，发现过去的阻塞同步模式模式根本不能拿到一个巨量多玩家[MMP]的架构中去，直接被否定掉了。于是乎，就有了IOCP，如果能过很轻易而举的搞掂IOCP，也就不会有这篇教程了。下面请诸位跟随我进入正题。</p>
<p>什么是IOCP？<br>先让我们看看对IOCP的评价<br>I/O完成端口可能是Win32提供的最复杂的内核对象。<br>[Advanced Windows 3rd] Jeffrey Richter<br>这是[IOCP]实现高容量网络服务器的最佳方法。<br>[Windows Sockets2.0:Write Scalable Winsock Apps Using Completion Ports] <br>Microsoft Corporation<br>完成端口模型提供了最好的伸缩性。这个模型非常适用来处理数百乃至上千个套接字。<br>[Windows网络编程2nd] Anthony Jones &amp; Jim Ohlund<br>I/O completion ports特别显得重要，因为它们是唯一适用于高负载服务器[必须同时维护许多连接线路]的一个技术。Completion ports利用一些线程，帮助平衡由I/O请求所引起的负载。这样的架构特别适合用在SMP系统中产生的&#8221;scalable&#8221;服务器。<br>[Win32多线程程序设计] Jim Beveridge &amp; Robert Wiener <br>&nbsp;<br>看来我们完全有理由相信IOCP是大型网络架构的首选。<br>那IOCP到底是什么呢？<br>　　微软在Winsock2中引入了IOCP这一概念 。IOCP全称I/O Completion Port，中文译为I/O完成端口。IOCP是一个异步I/O的API，它可以高效地将I/O事件通知给应用程序。与使用select()或是其它异步方法不同的是，一个套接字[socket]与一个完成端口关联了起来，然后就可继续进行正常的Winsock操作了。然而，当一个事件发生的时候，此完成端口就将被操作系统加入一个队列中。然后应用程序可以对核心层进行查询以得到此完成端口。<br>　　这里我要对上面的一些概念略作补充，在解释[完成]两字之前，我想先简单的提一下同步和异步这两个概念，逻辑上来讲做完一件事后再去做另一件事就是同步，而同时一起做两件或两件以上事的话就是异步了。你也可以拿单线程和多线程来作比喻。但是我们一定要将同步和堵塞，异步和非堵塞区分开来，所谓的堵塞函数诸如accept(&#8230;)，当调用此函数后，此时线程将挂起，直到操作系统来通知它，&#8221;HEY兄弟，有人连进来了&#8221;，那个挂起的线程将继续进行工作，也就符合&#8221;生产者-消费者&#8221;模型。堵塞和同步看上去有两分相似，但却是完全不同的概念。大家都知道I/O设备是个相对慢速的设备，不论打印机，调制解调器，甚至硬盘，与CPU相比都是奇慢无比的，坐下来等I/O的完成是一件不甚明智的事情，有时候数据的流动率非常惊人，把数据从你的文件服务器中以Ethernet速度搬走，其速度可能高达每秒一百万字节，如果你尝试从文件服务器中读取100KB，在用户的眼光来看几乎是瞬间完成，但是，要知道，你的线程执行这个命令，已经浪费了10个一百万次CPU周期。所以说，我们一般使用另一个线程来进行I/O。重叠IO[overlapped I/O]是Win32的一项技术，你可以要求操作系统为你传送数据，并且在传送完毕时通知你。这也就是[完成]的含义。这项技术使你的程序在I/O进行过程中仍然能够继续处理事务。事实上，操作系统内部正是以线程来完成overlapped I/O。你可以获得线程所有利益，而不需要付出什么痛苦的代价。<br>　　完成端口中所谓的[端口]并不是我们在TCP/IP中所提到的端口，可以说是完全没有关系。我到现在也没想通一个I/O设备[I/O Device]和端口[IOCP中的Port]有什么关系。估计这个端口也迷惑了不少人。IOCP只不过是用来进行读写操作，和文件I/O倒是有些类似。既然是一个读写设备，我们所能要求它的只是在处理读与写上的高效。在文章的第三部分你会轻而易举的发现IOCP设计的真正用意。</p>
<p>IOCP和网络又有什么关系？<br>int main()<br>{<br>&nbsp;&nbsp;&nbsp; WSAStartup(MAKEWORD(2, 2), &amp;wsaData);<br>&nbsp;&nbsp;&nbsp; ListeningSocket = socket(AF_INET, SOCK_STREAM, 0); <br>&nbsp;&nbsp;&nbsp; bind(ListeningSocket, (SOCKADDR*)&amp;ServerAddr, sizeof(ServerAddr));<br>&nbsp;&nbsp;&nbsp; listen(ListeningSocket, 5);<br>&nbsp;&nbsp;&nbsp; int nlistenAddrLen = sizeof(ClientAddr);<br>&nbsp;&nbsp;&nbsp; while(TRUE)<br>&nbsp;&nbsp;&nbsp; {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; NewConnection = accept(ListeningSocket, (SOCKADDR*)&amp;ClientAddr, &amp;nlistenAddrLen);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; HANDLE hThread = CreateThread(NULL, 0, ThreadFunc, (void*) NewConnection, 0, &amp;dwTreadId);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; CloseHandle(hThread);<br>&nbsp;&nbsp;&nbsp; }<br>&nbsp;&nbsp;&nbsp; return 0;<br>}<br>　　相信只要写过网络的朋友，应该对这样的结构在熟悉不过了。accept后线程被挂起，等待一个客户发出请求，而后创建新线程来处理请求。当新线程处理客户请求时，起初的线程循环回去等待另一个客户请求。处理客户请求的线程处理完毕后终结。<br>　　在上述的并发模型中，对每个客户请求都创建了一个线程。其优点在于等待请求的线程只需做很少的工作。大多数时间中，该线程在休眠[因为recv处于堵塞状态]。<br>　　但是当并发模型应用在服务器端[基于Windows NT]，Windows NT小组注意到这些应用程序的性能没有预料的那么高。特别的，处理很多同时的客户请求意味着很多线程并发地运行在系统中。因为所有这些线程都是可运行的[没有被挂起和等待发生什么事]，Microsoft意识到NT内核花费了太多的时间来转换运行线程的上下文[Context]，线程就没有得到很多CPU时间来做它们的工作。<br>　　大家可能也都感觉到并行模型的瓶颈在于它为每一个客户请求都创建了一个新线程。创建线程比起创建进程开销要小，但也远不是没有开销的。<br>　　我们不妨设想一下：如果事先开好N个线程，让它们在那hold[堵塞]，然后可以将所有用户的请求都投递到一个消息队列中去。然后那N个线程逐一从消息队列中去取出消息并加以处理。就可以避免针对每一个用户请求都开线程。不仅减少了线程的资源，也提高了线程的利用率。理论上很不错，你想我等泛泛之辈都能想出来的问题，Microsoft又怎会没有考虑到呢 !<br>　　这个问题的解决方法就是一个称为I/O完成端口的内核对象，他首次在Windows NT3.5中被引入。<br>　　其实我们上面的构想应该就差不多是IOCP的设计机理。其实说穿了IOCP不就是一个消息队列嘛！你说这和[端口]这两字有何联系。我的理解就是IOCP最多是应用程序和操作系统沟通的一个接口罢了。<br>　　至于IOCP的具体设计那我也很难说得上来，毕竟我没看过实现的代码，但你完全可以进行模拟，只不过性能可能&#8230;，如果想深入理解IOCP， Jeffrey Ritchter的Advanced Windows 3rd其中第13章和第14张有很多宝贵的内容，你可以拿来窥视一下系统是如何完成这一切的。<br>&nbsp;<br>实现方法<br>Microsoft为IOCP提供了相应的API函数，主要的就两个，我们逐一的来看一下：<br>HANDLE CreateIoCompletionPort (<br>&nbsp;&nbsp;&nbsp; HANDLE FileHandle,&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // handle to file<br>&nbsp;&nbsp;&nbsp; HANDLE ExistingCompletionPort,&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // handle to I/O completion port<br>&nbsp;&nbsp;&nbsp; ULONG_PTR CompletionKey,&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // completion key<br>&nbsp;&nbsp;&nbsp; DWORD NumberOfConcurrentThreads&nbsp; // number of threads to execute concurrently<br>);<br>在讨论各参数之前，首先要注意该函数实际用于两个截然不同的目的：<br>1．用于创建一个完成端口对象<br>2．将一个句柄[HANDLE]和完成端口关联到一起<br>　　在创建一个完成一个端口的时候，我们只需要填写一下NumberOfConcurrentThreads这个参数就可以了。它告诉系统一个完成端口上同时允许运行的线程最大数。在默认情况下，所开线程数和CPU数量相同，但经验给我们一个公式：<br>　　线程数 = CPU数 * 2 + 2<br>要使完成端口有用，你必须把它同一个或多个设备相关联。这也是调用CreateIoCompletionPort完成的。你要向该函数传递一个已有的完成端口的句柄，我们既然要处理网络事件，那也就是将客户的socket作为HANDLE传进去。和一个完成键[对你有意义的一个32位值，也就是一个指针，操作系统并不关心你传什么]。每当你向端口关联一个设备时，系统向该完成端口的设备列表中加入一条信息纪录。<br>另一个API就是<br>BOOL GetQueuedCompletionStatus(<br>&nbsp;&nbsp;&nbsp; HANDLE CompletionPort,&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // handle to completion port<br>&nbsp;&nbsp;&nbsp; LPDWORD lpNumberOfBytes,&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // bytes transferred<br>&nbsp;&nbsp;&nbsp; PULONG_PTR lpCompletionKey,&nbsp;&nbsp; // file completion key<br>&nbsp;&nbsp;&nbsp; LPOVERLAPPED *lpOverlapped,&nbsp;&nbsp; // buffer<br>&nbsp;&nbsp;&nbsp; DWORD dwMilliseconds&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 　&nbsp;&nbsp;&nbsp;&nbsp; // optional timeout value<br>);<br>第一个参数指出了线程要监视哪一个完成端口。很多服务应用程序只是使用一个I/O完成端口，所有的I/O请求完成以后的通知都将发给该端口。简单的说，GetQueuedCompletionStatus使调用线程挂起，直到指定的端口的I/O完成队列中出现了一项或直到超时。同I/O完成端口相关联的第3个数据结构是使线程得到完成I/O项中的信息：传输的字节数，完成键和OVERLAPPED结构的地址。该信息是通过传递给GetQueuedCompletionSatatus的lpdwNumberOfBytesTransferred，lpdwCompletionKey和lpOverlapped参数返回给线程的。<br>根据到目前为止已经讲到的东西，首先来构建一个frame。下面为您说明了如何使用完成端口来开发一个echo服务器。大致如下：<br>　　1.初始化Winsock<br>　　2.创建一个完成端口<br>　　3.根据服务器线程数创建一定量的线程数<br>　　4.准备好一个socket进行bind然后listen<br>　　5.进入循环accept等待客户请求<br>　　6.创建一个数据结构容纳socket和其他相关信息<br>　　7.将连进来的socket同完成端口相关联<br>　　8.投递一个准备接受的请求<br>以后就不断的重复5至8的过程<br>那好，我们用具体的代码来展示一下细节的操作。<br>　　至此文章也该告一段落了,我带着您做了一趟旋风般的旅游,游览了所谓的完成端口。<br>&nbsp;<br></p>
<img src ="http://www.cppblog.com/ccl0326/aggbug/110911.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/ccl0326/" target="_blank">Vincent</a> 2010-03-29 21:05 <a href="http://www.cppblog.com/ccl0326/archive/2010/03/29/110911.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>&lt;转&gt;c++智能指针的创建(个人觉得讲的超赞&amp;清晰)</title><link>http://www.cppblog.com/ccl0326/archive/2010/03/18/109974.html</link><dc:creator>Vincent</dc:creator><author>Vincent</author><pubDate>Thu, 18 Mar 2010 05:08:00 GMT</pubDate><guid>http://www.cppblog.com/ccl0326/archive/2010/03/18/109974.html</guid><wfw:comment>http://www.cppblog.com/ccl0326/comments/109974.html</wfw:comment><comments>http://www.cppblog.com/ccl0326/archive/2010/03/18/109974.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/ccl0326/comments/commentRss/109974.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/ccl0326/services/trackbacks/109974.html</trackback:ping><description><![CDATA[<p>zero 坐在餐桌前，机械的重复&#8220;夹菜 -&gt; 咀嚼 -&gt; 吞咽&#8221;的动作序列，脸上用无形的大字写着：我心不在焉。在他的对面坐着 Solmyr ，慢条斯理的吃着他那份午餐，维持着他一贯很有修养的形象 ——— 或者按照 zero 这些熟悉他本质的人的说法：假象。</p>
<p>&#8220;怎么了 zero ？胃口不好么？&#8221;，基本填饱肚子之后，Solmyr 觉得似乎应该关心一下他的学徒了。</p>
<p>&#8220;呃，没什么，只是 &#8230;&#8230; Solmyr ，C++ 为什么不支持垃圾收集呢？（注：垃圾收集是一种机制，保证动态分配了的内存块会自动释放，Java 等语言支持这一机制。）&#8221;</p>
<p>Solmyr 叹了口气，用一种平静的眼神盯着 zero ：&#8220;是不是在 BBS 上和人吵 C++ 和 Java 哪个更好？而且吵输了？我早告诉过你，这种争论再无聊不过了。&#8221;</p>
<p>&#8220;呃 &#8230;&#8230; 是&#8221;，zero 不得不承认 ——— Solmyr 的眼神虽然一点也不锐利，但是却莫名其妙的让 zero 产生了微微的恐惧感。</p>
<p>&#8220;而且，谁告诉你 C++ 不支持垃圾收集的？&#8221;</p>
<p>&#8220;啊！Solmyr 你不是开玩笑吧？！&#8221;</p>
<p>&#8220;zero 你得转变一下观念。我问你，C++ 支不支持可以动态改变大小的数组？&#8221;</p>
<p>&#8220;这 &#8230;&#8230; 好象也没有吧？&#8221;</p>
<p>&#8220;那 vector 是什么东西？&#8221;</p>
<p>&#8220;呃 &#8230;&#8230;&#8221;</p>
<p>&#8220;支持一种特性，并不是说非得把这个特性加到语法里去，我们也可以选择用现有的语言机制实现一个库来支持这个特征。以垃圾收集为例，这里我们的任务是要保证每一个被动态分配的内存块都能够被释放，也就是说 &#8230;&#8230;&#8221;，Solmyr 不知从哪里找出了一张纸、一支笔，写到：</p>
<p>&nbsp;</p>
<p>int* p = new int; // 1 <br>delete p; // 2</p>
<p>&nbsp;</p>
<p>&#8220;也就是说，对于每一个 1 ，我们要保证有一个 2 被调用，1 和 2 必须成对出现。我来问你，C++ 中有什么东西是由语言本身保证一定成对出现的？&#8221;</p>
<p>&#8220;&#8230;&#8230;&#8221;，zero 露出了努力搜索记忆的表情，不过很明显一无所获。</p>
<p>&#8220;提示一下，和类的创建有关。&#8221;</p>
<p>&#8220;哦！构造函数与析构函数！&#8221;</p>
<p>&#8220;正确。可惜普通指针没有构造函数与析构函数，所以我们必须要写一个类来加一层包装，最简单的就象这样：&#8221;</p>
<p>&nbsp;</p>
<p>class my_intptr <br>{ <br>&nbsp;&nbsp;&nbsp; public: <br>&nbsp;&nbsp;&nbsp; int* m_p;</p>
<p>&nbsp;&nbsp;&nbsp; my_intptr(int* p){ m_p = p; } <br>&nbsp;&nbsp;&nbsp; ~my_intptr(){ delete m_p; } <br>};</p>
<p>&#8230;&#8230;&#8230;&#8230;</p>
<p>my_intptr pi(new int); <br>*(pi.m_p) = 10;</p>
<p>&#8230;&#8230;&#8230;&#8230;</p>
<p>&nbsp;</p>
<p>&#8220;这里我们可以放心的使用 my_intptr ，不用担心内存泄漏的问题：一旦 pi 这个变量被销毁，我们知道 pi.p 指向的内存块一定会被释放。不过如果每次使用 my_intptr 都得去访问它的成员未免太麻烦了。为此，可以给这个类加上重载的 * 运算符：&#8221;</p>
<p>&nbsp;</p>
<p>class my_intptr <br>{ <br>&nbsp;&nbsp;&nbsp; private: <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; int* m_p;</p>
<p>&nbsp;&nbsp;&nbsp; public: <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; my_intptr(int* p){ m_p = p; } <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ~my_intptr(){ delete m_p; }</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; int&amp; operator*(){ return *m_p; } <br>};</p>
<p>&#8230;&#8230;&#8230;&#8230;</p>
<p>my_intptr pi; <br>*pi = 10; <br>int a = *pi;</p>
<p>&#8230;&#8230;&#8230;&#8230;</p>
<p>&nbsp;</p>
<p>&#8220;现在是不是看起来 my_intptr 就像是一个真正的指针了？正因为如此，这种技术被称为智能指针。现在我问你，这个类还缺少哪些东西？&#8221;</p>
<p>zero 皱着眉头，眼睛一眨一眨，看上去就像一台慢速电脑正在辛苦的往它的硬盘上拷贝文件。良久，zero 抬起头来，不太确定的说：&#8220;是不是还缺少一个拷贝构造函数和一个赋值运算符？&#8221;</p>
<p>&#8220;说说为什么。&#8221;，Solmyr 显然不打算就这样放过 zero。</p>
<p>&#8220;因为 &#8230;&#8230; 我记得没错的话 &#8230;&#8230; 《50 诫 》（注：指《Effective C++ 2/e》一书）中提到过，如果你的类里面有指针指向动态分配的内存，那么一定要为它写一个拷贝构造函数和一个赋值运算符 &#8230;&#8230; 因为 &#8230;&#8230; 否则的话，一旦你做了赋值，会导致两个对象的指针指向同一块内存。对了！如果是上面的类，这样一来会导致同一个指针被 delete 两次！&#8221;</p>
<p>&#8220;正确。那么我们应该怎样来实现呢？&#8221;</p>
<p>&#8220;这简单，我们用 memcpy 把目标指针指向的内存中的内容拷贝过来。&#8221;</p>
<p>&#8220;如果我们的智能指针指向一个类的对象怎么办？注意，类的对象中可能有指针，不能用 memcpy。&#8221;</p>
<p>&#8220;那 &#8230;&#8230; 我们用拷贝构造的办法。&#8221;</p>
<p>&#8220;如果我们的智能指针指向的对象不能拷贝构造怎么办？它可能有一个私有的拷贝构造函数。&#8221;</p>
<p>&#8220;那 &#8230;&#8230;&#8221;，zero 顿了一顿，决定老实承认，&#8220;我不知道。&#8221;</p>
<p>&#8220;问题在哪你知道么？在于你没有把智能指针看作指针。想象一下，如果我们对一个指针做赋值，它的含义是什么？&#8221;</p>
<p>&#8220;呃，我明白了，在这种情况下，应该想办法让两个智能指针指向同一个对象 &#8230;&#8230; 可是 Solmyr ，这样以来岂不是仍然要对同一个对象删除两遍？&#8221;</p>
<p>&#8220;是的，我们得想办法解决这个问题，办法不只一种。比较好的一种是为每个指针维护一个引用计数值，每次赋值或者拷贝构造，就让计数值加一，这意味着指向这个内存块的智能指针又多了一个；而每有一个智能指针被销毁，就让计数值减一，这意味着指向这个内存块的智能指针少了一个；一旦计数值为 0 ，就释放内存块。象这样：&#8221;</p>
<p>&nbsp;</p>
<p>class my_intptr <br>{ <br>&nbsp;&nbsp;&nbsp; private: <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; int* m_p; <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; int* m_count;</p>
<p>&nbsp;&nbsp;&nbsp; public: <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; my_intptr(int* p) <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; { <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; m_p = p; <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; m_count = new int;</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // 初始化计数值为 1 <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; *m_count = 1; <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; } <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; my_intptr(const my_intptr&amp; rhs) // 拷贝构造函数 <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; { <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; m_p = rhs.m_p; // 指向同一块内存 <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; m_count = rhs.m_count; // 使用同一个计数值 <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; (*m_count)++; // 计数值加 1 <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; } <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ~my_intptr() <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; { <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; (*m_count)--; // 计数值减 1 <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if( *m_count == 0 ) // 已经没有别的指针指向该内存块了 <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; { <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; delete m_p; <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; delete m_count; <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; } <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; my_intptr&amp; operator=(const my_intptr&amp; rhs) <br>&nbsp;&nbsp;&nbsp;&nbsp; { <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if( m_p == rhs.m_p ) // 首先判断是否本来就指向同一内存块 <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return *this; // 是则直接返回</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; (*m_count)--; // 计数值减 1 ，因为该指针不再指向原来内存块了 <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if( *m_count == 0 ) // 已经没有别的指针指向原来内存块了 <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; { <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; delete m_p; <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; delete m_count; <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; m_p = rhs.m_p; // 指向同一块内存 <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; m_count = rhs.m_count; // 使用同一个计数值 <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; (*m_count)++; // 计数值加 1 <br>&nbsp;&nbsp;&nbsp; }</p>
<p>&#8230;&#8230;&#8230;&#8230; <br>};</p>
<p>&nbsp;</p>
<p>&#8220;其他部分没有什么太大变化，我不费事了。现在想象一下我们怎样使用这种智能指针？&#8221;，Solmyr 放下了笔，再次拿起了筷子，有些惋惜的发现他爱吃的肉丸子已经冷了。</p>
<p>zero 想象着，有些迟疑。&#8220;我们 &#8230;&#8230; 可以用 new int 表达式作为构造函数的参数来构造一个智能指针，然后 &#8230;&#8230; 然后我们可以任意的赋值，&#8221;，他开始抓住了思路，越说越快，&#8220;任意的用已经存在的智能指针来构造新的智能指针，智能指针的赋值运算符、拷贝构造函数和析构会保证计数值始终等于指向该内存块的智能指针数。&#8221;zero 似乎明白了他看到了怎样的功能，开始激动起来：&#8220;然后一旦计数值为 0 被分配的内存块就会释放！也就是说 &#8230;&#8230; 有指针指向内存块，它就不释放，一旦没有，它就自动释放！太棒了！我们只要一开始正确的初始化智能指针，就可以象普通指针那样使用它，而且完全不用担心内存释放的问题！太棒了！&#8221;zero 激动的大叫：&#8220;这就是垃圾收集！Solmyr ！我们在饭桌上实现了一个垃圾收集器！&#8221;</p>
<p>Solmyr 很明显没有分享 zero 的激动：&#8220;我在吃饭，你能不能不要大叫&#8216;饭桌上实现了一个垃圾收集器&#8217;这种倒胃口的话？&#8221;顿了一顿，Solmyr 带着他招牌式的坏笑，以一种可恶的口吻说道：&#8220;而且请注意一下自己的形象。&#8221;</p>
<p>&#8220;嗯？&#8221;，zero 回过神来，发现自己不知什么时候站了起来，而整个餐厅里的人都在看着他嘿嘿偷笑，这让他感觉自己像个傻瓜。</p>
<p>zero 红着脸坐下，压低了声音问 Solmyr ：&#8220;不过 Solmyr ，这确实是一个的垃圾收集机制啊，只要我们把这个类改成 &#8230;&#8230; 嗯 &#8230;&#8230; 改成模板类，象这样：&#8221;zero 抓过了纸笔，写到：</p>
<p>&nbsp;</p>
<p>template &lt;typename T&gt; <br>class my_ptr <br>{ <br>&nbsp;&nbsp;&nbsp; private: <br>&nbsp;&nbsp;&nbsp; T* m_p; <br>&nbsp;&nbsp;&nbsp; int* m_count; <br>&nbsp;&nbsp;&nbsp; &#8230;&#8230;&#8230;&#8230; <br>};</p>
<p>&nbsp;</p>
<p>&#8220;它不就能支持任意类型的指针了吗？我们就可以把它用在任何地方。&#8221;</p>
<p>Solmyr 摇了摇头：&#8220;不，你把问题想的太简单了。对于简单的类型，这个类确实可以处理的很好，但实际情况是很复杂的。考虑一个典型情况：类 Derived 是类 Base 的派生类，我们希望这样赋值：&#8221;</p>
<p>Base* pb; <br>Derived pd; <br>&#8230;&#8230;&#8230;&#8230; <br>pb = pd;</p>
<p>&#8220;你倒说说看，这种情况，怎样改用上面这个智能指针来处理？&#8221;</p>
<p>&#8220;&#8230;&#8230;&#8221;，zero 沉默了。</p>
<p>&#8220;要实现一个完整的垃圾收集机制并不容易，因为有许多细节要考虑。&#8221;，Solmyr 开始总结了，&#8220;不过，基本思路就是上面说的这些。值得庆幸的是，目前已经有了一个相当成熟的&#8216;引用计数&#8217;智能指针，boost::shared_ptr。大多数情况下，我们都可以使用它。另外，除了智能指针之外，还有一些技术也能够帮助我们避开释放内存的问题，比如内存池。但是，关键在于 ——— &#8221;</p>
<p>Solmyr 再度用那种平静的眼神盯着 zero ：</p>
<p>&#8220;身为 C/C++ 程序员，必须有创造力。那种躺在语言机制上不思进取的人，那种必须要靠语法强制才知道怎样编程的人，那种没有别人告诉他该干什么就无所适从的人，不适合这门语言。&#8221;</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>欢迎访问梦断酒醒的博客：<a href="http://www.yanzhijun.com/">http://www.yanzhijun.com</a></p>
<p>&nbsp;</p>
<p>本文来自CSDN博客，转载请标明出处：<a href="http://blog.csdn.net/ishallwin/archive/2009/09/08/4533145.aspx">http://blog.csdn.net/ishallwin/archive/2009/09/08/4533145.aspx</a></p>
<img src ="http://www.cppblog.com/ccl0326/aggbug/109974.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/ccl0326/" target="_blank">Vincent</a> 2010-03-18 13:08 <a href="http://www.cppblog.com/ccl0326/archive/2010/03/18/109974.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item></channel></rss>