﻿<?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/SpringSnow/category/8521.html</link><description>雪化了，花开了，春天来了</description><language>zh-cn</language><lastBuildDate>Wed, 29 Jul 2009 03:52:41 GMT</lastBuildDate><pubDate>Wed, 29 Jul 2009 03:52:41 GMT</pubDate><ttl>60</ttl><item><title>转：位运算符的一些简单应用 </title><link>http://www.cppblog.com/SpringSnow/articles/91499.html</link><dc:creator>Sandy</dc:creator><author>Sandy</author><pubDate>Tue, 28 Jul 2009 08:21:00 GMT</pubDate><guid>http://www.cppblog.com/SpringSnow/articles/91499.html</guid><wfw:comment>http://www.cppblog.com/SpringSnow/comments/91499.html</wfw:comment><comments>http://www.cppblog.com/SpringSnow/articles/91499.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/SpringSnow/comments/commentRss/91499.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/SpringSnow/services/trackbacks/91499.html</trackback:ping><description><![CDATA[今天看的一些东西用到了位运算符，这里有一篇文章也谈了一些位运算符的简单操作。还不错。<br>转过来了。<br><br>摘自：<a href="http://www.cppblog.com/pengkuny/archive/2007/04/21/22551.html">http://www.cppblog.com/pengkuny/archive/2007/04/21/22551.html</a><br><br>
<div class=postTitle><a class=postTitle2 id=viewpost1_TitleUrl href="http://www.cppblog.com/pengkuny/archive/2007/04/21/22551.html"><u><font color=#800080>位运算符的一些简单应用</font></u></a> </div>
<p>位运算符有：&amp;(按位与)、|(按位或)、^(按位异或)、~ (按位取反)。 <br>优先级从高到低，依次为~、&amp;、^、|， </p>
<p>位运算符的一些简单应用:<br>按位与运算有两种典型用法，一是取一个位串信息的某几位，如以下代码截取x的最低7位：x &amp; 0177。二是让某变量保留某几位，其余位置0，如以下代码让x只保留最低6位：x = x &amp; 077。</p>
<p>按位或运算的典型用法是将一个位串信息的某几位置成1。如将要获得最右4为1，其他位与变量j的其他位相同，可用逻辑或运算017|j</p>
<p>按位异或运算的典型用法是求一个位串信息的某几位信息的反。如欲求整型变量j的最右4位信息的反，用逻辑异或运算017^j，就能求得j最右4位的信息的反,即原来为1的位，结果是0,原来为0的位，结果是1。 交换两个值，不用临时变量,假如a=3,b=4。想将a和b的值互换，可以用以下赋值语句实现：<br>a=a^b;&nbsp; b=b^a;&nbsp;&nbsp;&nbsp;&nbsp; a=a^b;</p>
<p>取反运算常用来生成与系统实现无关的常数。如要将变量x最低6位置成0，其余位不变，可用代码x = x &amp; ~077实现。以上代码与整数x用2个字节还是用4个字节实现无关。 <br>当两个长度不同的数据进行位运算时(例如long型数据与int型数据)，将两个运算分量的右端对齐进行位运算。如果短的数为正数，高位用0补满；如果短的数为负数，高位用1补满。如果短的为无符号整数，则高位总是用0补满。</p>
<p>位运算用来对位串信息进行运算，得到位串信息结果。如以下代码能取下整型变量k的位串信息的最右边为1的信息位：((k-1)^k) &amp; k。 </p>
<p>对于带符号的数据，如果移位前符号位为0(正数)，则左端也是用0补充；如果移位前符号位为1(负数)，则左端用0或用1补充，取决于计算机系统。对于负数右移，称用0 补充的系统为&#8220;逻辑右移&#8221;，用1补充的系统为&#8220;算术右移&#8221;。以下代码能说明读者上机的系统所采用的右移方法： <br>&nbsp;&nbsp;&nbsp;&nbsp; printf("%d\n\n\n", -2&gt;&gt;4); <br>若输出结果为-1，是采用算术右移；输出结果为一个大整数，则为逻辑右移。</p>
<p>移位运算与位运算结合能实现许多与位串运算有关的复杂计算。设变量的位自右至左顺序编号，自0位至15位，有关指定位的表达式是不超过15的正整数。以下各代码分别有它们右边注释所示的意义：<br>&nbsp;&nbsp; (1) 判断int型变量a是奇数还是偶数,尤其是对大数的判断<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; a&amp;1 == 0 偶数<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; a&amp;1 == 1 奇数<br>&nbsp;&nbsp;&nbsp; (2) 取int型变量a的第k位 (k=0,1,2&#8230;&#8230;sizeof(int))<br>&nbsp;a&gt;&gt;k&amp;1<br>&nbsp;&nbsp;&nbsp; (3) 将int型变量a的第k位清0<br>&nbsp;a=a&amp;~(1&lt;&lt;k)<br>&nbsp;&nbsp;&nbsp; (4) 将int型变量a的第k位置1<br>&nbsp;a=a|(1&lt;&lt;k)<br>&nbsp;&nbsp;&nbsp; (5) int型变量循环左移k次<br>&nbsp;a=a&lt;&lt;k|a&gt;&gt;16-k&nbsp;&nbsp; (设sizeof(int)=16)<br>&nbsp;&nbsp;&nbsp; (6) int型变量a循环右移k次<br>&nbsp;a=a&gt;&gt;k|a&lt;&lt;16-k&nbsp;&nbsp; (设sizeof(int)=16)<br>&nbsp;&nbsp;&nbsp; (7) 实现最低n位为1，其余位为0的位串信息:<br>&nbsp;~（~0 &lt;&lt; n） <br>&nbsp;&nbsp;&nbsp; (8)截取变量x自p位开始的右边n位的信息:<br>&nbsp;(x &gt;&gt; (1+p-n)) &amp; ~(~0 &lt;&lt; n)&nbsp; <br>&nbsp;&nbsp;&nbsp; (9)截取old变量第row位，并将该位信息装配到变量new的第15-k位<br>&nbsp;new |= ((old &gt;&gt; row) &amp; 1) &lt;&lt; (15 &#8211; k)<br>&nbsp;&nbsp;&nbsp; (10)设s不等于全0，代码寻找最右边为1的位的序号j:<br>&nbsp;for(j = 0; ((1 &lt;&lt; j) &amp; s) == 0; j++) ; </p>
<br>有些用法还未考证
<img src ="http://www.cppblog.com/SpringSnow/aggbug/91499.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/SpringSnow/" target="_blank">Sandy</a> 2009-07-28 16:21 <a href="http://www.cppblog.com/SpringSnow/articles/91499.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>转: C++ 虚函数表解析</title><link>http://www.cppblog.com/SpringSnow/articles/89181.html</link><dc:creator>Sandy</dc:creator><author>Sandy</author><pubDate>Fri, 03 Jul 2009 09:32:00 GMT</pubDate><guid>http://www.cppblog.com/SpringSnow/articles/89181.html</guid><wfw:comment>http://www.cppblog.com/SpringSnow/comments/89181.html</wfw:comment><comments>http://www.cppblog.com/SpringSnow/articles/89181.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/SpringSnow/comments/commentRss/89181.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/SpringSnow/services/trackbacks/89181.html</trackback:ping><description><![CDATA[<p>C++ 虚函数表解析</p>
<p>&nbsp;</p>
<p>陈皓</p>
<p><a href="http://blog.csdn.net/haoel">http://blog.csdn.net/haoel</a></p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>前言<br>&nbsp;</p>
<p>C++中的虚函数的作用主要是实现了多态的机制。关于多态，简而言之就是用父类型别的指针指向其子类的实例，然后通过父类的指针调用实际子类的成员函数。这种技术可以让父类的指针有&#8220;多种形态&#8221;，这是一种泛型技术。所谓泛型技术，说白了就是试图使用不变的代码来实现可变的算法。比如：模板技术，RTTI技术，虚函数技术，要么是试图做到在编译时决议，要么试图做到运行时决议。</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>关于虚函数的使用方法，我在这里不做过多的阐述。大家可以看看相关的C++的书籍。在这篇文章中，我只想从虚函数的实现机制上面为大家 一个清晰的剖析。</p>
<p>&nbsp;</p>
<p>当然，相同的文章在网上也出现过一些了，但我总感觉这些文章不是很容易阅读，大段大段的代码，没有图片，没有详细的说明，没有比较，没有举一反三。不利于学习和阅读，所以这是我想写下这篇文章的原因。也希望大家多给我提意见。</p>
<p>&nbsp;</p>
<p>言归正传，让我们一起进入虚函数的世界。</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>虚函数表<br>&nbsp;</p>
<p>对C++ 了解的人都应该知道虚函数（Virtual Function）是通过一张虚函数表（Virtual Table）来实现的。简称为V-Table。在这个表中，主是要一个类的虚函数的地址表，这张表解决了继承、覆盖的问题，保证其容真实反应实际的函数。这样，在有虚函数的类的实例中这个表被分配在了这个实例的内存中，所以，当我们用父类的指针来操作一个子类的时候，这张虚函数表就显得由为重要了，它就像一个地图一样，指明了实际所应该调用的函数。</p>
<p>&nbsp;</p>
<p>这里我们着重看一下这张虚函数表。C++的编译器应该是保证虚函数表的指针存在于对象实例中最前面的位置（这是为了保证取到虚函数表的有最高的性能——如果有多层继承或是多重继承的情况下）。 这意味着我们通过对象实例的地址得到这张虚函数表，然后就可以遍历其中函数指针，并调用相应的函数。</p>
<p>&nbsp;</p>
<p>听我扯了那么多，我可以感觉出来你现在可能比以前更加晕头转向了。 没关系，下面就是实际的例子，相信聪明的你一看就明白了。</p>
<p>&nbsp;</p>
<p>假设我们有这样的一个类：</p>
<p>&nbsp;</p>
<p>class Base {</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp; public:</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; virtual void f() { cout &lt;&lt; "Base::f" &lt;&lt; endl; }</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; virtual void g() { cout &lt;&lt; "Base::g" &lt;&lt; endl; }</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; virtual void h() { cout &lt;&lt; "Base::h" &lt;&lt; endl; }</p>
<p>&nbsp;</p>
<p>};</p>
<p>&nbsp;</p>
<p>按照上面的说法，我们可以通过Base的实例来得到虚函数表。 下面是实际例程：</p>
<p>&nbsp;</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; typedef void(*Fun)(void);</p>
<p>&nbsp;</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Base b;</p>
<p>&nbsp;</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Fun pFun = NULL;</p>
<p>&nbsp;</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; cout &lt;&lt; "虚函数表地址：" &lt;&lt; (int*)(&amp;b) &lt;&lt; endl;</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; cout &lt;&lt; "虚函数表 — 第一个函数地址：" &lt;&lt; (int*)*(int*)(&amp;b) &lt;&lt; endl;</p>
<p>&nbsp;</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // Invoke the first virtual function&nbsp; </p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; pFun = (Fun)*((int*)*(int*)(&amp;b));</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; pFun();</p>
<p>&nbsp;</p>
<p>实际运行经果如下：(Windows XP+VS2003,&nbsp; Linux 2.6.22 + GCC 4.1.3)</p>
<p>&nbsp;</p>
<p>虚函数表地址：0012FED4</p>
<p>虚函数表 — 第一个函数地址：0044F148</p>
<p>Base::f</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>通过这个示例，我们可以看到，我们可以通过强行把&amp;b转成int *，取得虚函数表的地址，然后，再次取址就可以得到第一个虚函数的地址了，也就是Base::f()，这在上面的程序中得到了验证（把int* 强制转成了函数指针）。通过这个示例，我们就可以知道如果要调用Base::g()和Base::h()，其代码如下：</p>
<p>&nbsp;</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; (Fun)*((int*)*(int*)(&amp;b)+0);&nbsp; // Base::f()</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; (Fun)*((int*)*(int*)(&amp;b)+1);&nbsp; // Base::g()</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; (Fun)*((int*)*(int*)(&amp;b)+2);&nbsp; // Base::h()</p>
<p>&nbsp;</p>
<p>这个时候你应该懂了吧。什么？还是有点晕。也是，这样的代码看着太乱了。没问题，让我画个图解释一下。如下所示：</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p><br>注意：在上面这个图中，我在虚函数表的最后多加了一个结点，这是虚函数表的结束结点，就像字符串的结束符&#8220;\0&#8221;一样，其标志了虚函数表的结束。这个结束标志的值在不同的编译器下是不同的。在WinXP+VS2003下，这个值是NULL。而在Ubuntu 7.10 + Linux 2.6.22 + GCC 4.1.3下，这个值是如果1，表示还有下一个虚函数表，如果值是0，表示是最后一个虚函数表。</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>下面，我将分别说明&#8220;无覆盖&#8221;和&#8220;有覆盖&#8221;时的虚函数表的样子。没有覆盖父类的虚函数是毫无意义的。我之所以要讲述没有覆盖的情况，主要目的是为了给一个对比。在比较之下，我们可以更加清楚地知道其内部的具体实现。</p>
<p>&nbsp;</p>
<p>一般继承（无虚函数覆盖）<br>&nbsp;</p>
<p>下面，再让我们来看看继承时的虚函数表是什么样的。假设有如下所示的一个继承关系：</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p><br>&nbsp;</p>
<p>请注意，在这个继承关系中，子类没有重载任何父类的函数。那么，在派生类的实例中，其虚函数表如下所示：</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>对于实例：Derive d; 的虚函数表如下：</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>我们可以看到下面几点：</p>
<p>1）虚函数按照其声明顺序放于表中。</p>
<p>2）父类的虚函数在子类的虚函数前面。</p>
<p>&nbsp;</p>
<p>我相信聪明的你一定可以参考前面的那个程序，来编写一段程序来验证。</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>一般继承（有虚函数覆盖）<br>&nbsp;</p>
<p>覆盖父类的虚函数是很显然的事情，不然，虚函数就变得毫无意义。下面，我们来看一下，如果子类中有虚函数重载了父类的虚函数，会是一个什么样子？假设，我们有下面这样的一个继承关系。</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p><br>&nbsp;</p>
<p>为了让大家看到被继承过后的效果，在这个类的设计中，我只覆盖了父类的一个函数：f()。那么，对于派生类的实例，其虚函数表会是下面的一个样子：</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p><br>&nbsp;</p>
<p><br>我们从表中可以看到下面几点，</p>
<p>1）覆盖的f()函数被放到了虚表中原来父类虚函数的位置。</p>
<p>2）没有被覆盖的函数依旧。</p>
<p>&nbsp;</p>
<p>这样，我们就可以看到对于下面这样的程序，</p>
<p>&nbsp;</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Base *b = new Derive();</p>
<p>&nbsp;</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; b-&gt;f();</p>
<p>&nbsp;</p>
<p>由b所指的内存中的虚函数表的f()的位置已经被Derive::f()函数地址所取代，于是在实际调用发生时，是Derive::f()被调用了。这就实现了多态。</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>多重继承（无虚函数覆盖）<br>&nbsp;</p>
<p>下面，再让我们来看看多重继承中的情况，假设有下面这样一个类的继承关系。注意：子类并没有覆盖父类的函数。</p>
<p>&nbsp;</p>
<p><br>&nbsp;</p>
<p>&nbsp;</p>
<p>对于子类实例中的虚函数表，是下面这个样子：</p>
<p>&nbsp;</p>
<p><br>&nbsp;</p>
<p><br>我们可以看到：</p>
<p>1）&nbsp; 每个父类都有自己的虚表。</p>
<p>2）&nbsp; 子类的成员函数被放到了第一个父类的表中。（所谓的第一个父类是按照声明顺序来判断的）</p>
<p>&nbsp;</p>
<p>这样做就是为了解决不同的父类类型的指针指向同一个子类实例，而能够调用到实际的函数。</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>多重继承（有虚函数覆盖）<br>&nbsp;</p>
<p>下面我们再来看看，如果发生虚函数覆盖的情况。</p>
<p>&nbsp;</p>
<p>下图中，我们在子类中覆盖了父类的f()函数。</p>
<p>&nbsp;</p>
<p><br>&nbsp;</p>
<p>&nbsp;</p>
<p>下面是对于子类实例中的虚函数表的图：</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p><br>&nbsp;</p>
<p>我们可以看见，三个父类虚函数表中的f()的位置被替换成了子类的函数指针。这样，我们就可以任一静态类型的父类来指向子类，并调用子类的f()了。如：</p>
<p>&nbsp;</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Derive d;</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Base1 *b1 = &amp;d;</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Base2 *b2 = &amp;d;</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Base3 *b3 = &amp;d;</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; b1-&gt;f(); //Derive::f()</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; b2-&gt;f(); //Derive::f()</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; b3-&gt;f(); //Derive::f()</p>
<p>&nbsp;</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; b1-&gt;g(); //Base1::g()</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; b2-&gt;g(); //Base2::g()</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; b3-&gt;g(); //Base3::g()</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>安全性<br>&nbsp;</p>
<p>每次写C++的文章，总免不了要批判一下C++。这篇文章也不例外。通过上面的讲述，相信我们对虚函数表有一个比较细致的了解了。水可载舟，亦可覆舟。下面，让我们来看看我们可以用虚函数表来干点什么坏事吧。</p>
<p>&nbsp;</p>
<p>一、通过父类型的指针访问子类自己的虚函数</p>
<p>我们知道，子类没有重载父类的虚函数是一件毫无意义的事情。因为多态也是要基于函数重载的。虽然在上面的图中我们可以看到Base1的虚表中有Derive的虚函数，但我们根本不可能使用下面的语句来调用子类的自有虚函数：</p>
<p>&nbsp;</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Base1 *b1 = new Derive();</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; b1-&gt;f1();&nbsp; //编译出错</p>
<p>&nbsp;</p>
<p>任何妄图使用父类指针想调用子类中的未覆盖父类的成员函数的行为都会被编译器视为非法，所以，这样的程序根本无法编译通过。但在运行时，我们可以通过指针的方式访问虚函数表来达到违反C++语义的行为。（关于这方面的尝试，通过阅读后面附录的代码，相信你可以做到这一点）</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>二、访问non-public的虚函数</p>
<p>另外，如果父类的虚函数是private或是protected的，但这些非public的虚函数同样会存在于虚函数表中，所以，我们同样可以使用访问虚函数表的方式来访问这些non-public的虚函数，这是很容易做到的。</p>
<p>&nbsp;</p>
<p>如：</p>
<p>&nbsp;</p>
<p>class Base {</p>
<p>&nbsp;&nbsp;&nbsp; private:</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; virtual void f() { cout &lt;&lt; "Base::f" &lt;&lt; endl; }</p>
<p>&nbsp;</p>
<p>};</p>
<p>&nbsp;</p>
<p>class Derive : public Base{</p>
<p>&nbsp;</p>
<p>};</p>
<p>&nbsp;</p>
<p>typedef void(*Fun)(void);</p>
<p>&nbsp;</p>
<p>void main() {</p>
<p>&nbsp;&nbsp;&nbsp; Derive d;</p>
<p>&nbsp;&nbsp;&nbsp; Fun&nbsp; pFun = (Fun)*((int*)*(int*)(&amp;d)+0);</p>
<p>&nbsp;&nbsp;&nbsp; pFun();</p>
<p>}</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>结束语<br>C++这门语言是一门Magic的语言，对于程序员来说，我们似乎永远摸不清楚这门语言背着我们在干了什么。需要熟悉这门语言，我们就必需要了解C++里面的那些东西，需要去了解C++中那些危险的东西。不然，这是一种搬起石头砸自己脚的编程语言。</p>
<p>&nbsp;</p>
<p>在文章束之前还是介绍一下自己吧。我从事软件研发有十个年头了，目前是软件开发技术主管，技术方面，主攻Unix/C/C++，比较喜欢网络上的技术，比如分布式计算，网格计算，P2P，Ajax等一切和互联网相关的东西。管理方面比较擅长于团队建设，技术趋势分析，项目管理。欢迎大家和我交流，我的MSN和Email是：<a href="&#109;&#97;&#105;&#108;&#116;&#111;&#58;&#104;&#97;&#111;&#101;&#108;&#64;&#104;&#111;&#116;&#109;&#97;&#105;&#108;&#46;&#99;&#111;&#109;">haoel@hotmail.com</a>&nbsp; </p>
<p>&nbsp;</p>
<p>附录一：VC中查看虚函数表<br>&nbsp;</p>
<p>我们可以在VC的IDE环境中的Debug状态下展开类的实例就可以看到虚函数表了（并不是很完整的）</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>附录 二：例程<br>下面是一个关于多重继承的虚函数表访问的例程：</p>
<p>&nbsp;</p>
<p>#include &lt;iostream&gt;</p>
<p>using namespace std;</p>
<p>&nbsp;</p>
<p>class Base1 {</p>
<p>public:</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; virtual void f() { cout &lt;&lt; "Base1::f" &lt;&lt; endl; }</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; virtual void g() { cout &lt;&lt; "Base1::g" &lt;&lt; endl; }</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; virtual void h() { cout &lt;&lt; "Base1::h" &lt;&lt; endl; }</p>
<p>&nbsp;</p>
<p>};</p>
<p>&nbsp;</p>
<p>class Base2 {</p>
<p>public:</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; virtual void f() { cout &lt;&lt; "Base2::f" &lt;&lt; endl; }</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; virtual void g() { cout &lt;&lt; "Base2::g" &lt;&lt; endl; }</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; virtual void h() { cout &lt;&lt; "Base2::h" &lt;&lt; endl; }</p>
<p>};</p>
<p>&nbsp;</p>
<p>class Base3 {</p>
<p>public:</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; virtual void f() { cout &lt;&lt; "Base3::f" &lt;&lt; endl; }</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; virtual void g() { cout &lt;&lt; "Base3::g" &lt;&lt; endl; }</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; virtual void h() { cout &lt;&lt; "Base3::h" &lt;&lt; endl; }</p>
<p>};</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>class Derive : public Base1, public Base2, public Base3 {</p>
<p>public:</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; virtual void f() { cout &lt;&lt; "Derive::f" &lt;&lt; endl; }</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; virtual void g1() { cout &lt;&lt; "Derive::g1" &lt;&lt; endl; }</p>
<p>};</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>typedef void(*Fun)(void);</p>
<p>&nbsp;</p>
<p>int main() </p>
<p>{</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Fun pFun = NULL;</p>
<p>&nbsp;</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Derive d;</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; int** pVtab = (int**)&amp;d;</p>
<p>&nbsp;</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //Base1's vtable</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //pFun = (Fun)*((int*)*(int*)((int*)&amp;d+0)+0);</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; pFun = (Fun)pVtab[0][0];</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; pFun();</p>
<p>&nbsp;</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //pFun = (Fun)*((int*)*(int*)((int*)&amp;d+0)+1);</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; pFun = (Fun)pVtab[0][1];</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; pFun();</p>
<p>&nbsp;</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //pFun = (Fun)*((int*)*(int*)((int*)&amp;d+0)+2);</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; pFun = (Fun)pVtab[0][2];</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; pFun();</p>
<p>&nbsp;</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //Derive's vtable</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //pFun = (Fun)*((int*)*(int*)((int*)&amp;d+0)+3);</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; pFun = (Fun)pVtab[0][3];</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; pFun();</p>
<p>&nbsp;</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //The tail of the vtable</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; pFun = (Fun)pVtab[0][4];</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; cout&lt;&lt;pFun&lt;&lt;endl;</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //Base2's vtable</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //pFun = (Fun)*((int*)*(int*)((int*)&amp;d+1)+0);</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; pFun = (Fun)pVtab[1][0];</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; pFun();</p>
<p>&nbsp;</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //pFun = (Fun)*((int*)*(int*)((int*)&amp;d+1)+1);</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; pFun = (Fun)pVtab[1][1];</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; pFun();</p>
<p>&nbsp;</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; pFun = (Fun)pVtab[1][2];</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; pFun(); </p>
<p>&nbsp;</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //The tail of the vtable</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; pFun = (Fun)pVtab[1][3];</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; cout&lt;&lt;pFun&lt;&lt;endl;</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //Base3's vtable</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //pFun = (Fun)*((int*)*(int*)((int*)&amp;d+1)+0);</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; pFun = (Fun)pVtab[2][0];</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; pFun();</p>
<p>&nbsp;</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //pFun = (Fun)*((int*)*(int*)((int*)&amp;d+1)+1);</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; pFun = (Fun)pVtab[2][1];</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; pFun();</p>
<p>&nbsp;</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; pFun = (Fun)pVtab[2][2];</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; pFun(); </p>
<p>&nbsp;</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //The tail of the vtable</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; pFun = (Fun)pVtab[2][3];</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; cout&lt;&lt;pFun&lt;&lt;endl;</p>
<p>&nbsp;</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return 0;</p>
<p>}</p>
<p>&nbsp;</p>
<p>本文来自CSDN博客，转载请标明出处：<a href="http://blog.csdn.net/haoel/archive/2007/12/18/1948051.aspx">http://blog.csdn.net/haoel/archive/2007/12/18/1948051.aspx</a></p>
<img src ="http://www.cppblog.com/SpringSnow/aggbug/89181.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/SpringSnow/" target="_blank">Sandy</a> 2009-07-03 17:32 <a href="http://www.cppblog.com/SpringSnow/articles/89181.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>转: C++ 对象的内存布局(下)</title><link>http://www.cppblog.com/SpringSnow/articles/89178.html</link><dc:creator>Sandy</dc:creator><author>Sandy</author><pubDate>Fri, 03 Jul 2009 09:21:00 GMT</pubDate><guid>http://www.cppblog.com/SpringSnow/articles/89178.html</guid><wfw:comment>http://www.cppblog.com/SpringSnow/comments/89178.html</wfw:comment><comments>http://www.cppblog.com/SpringSnow/articles/89178.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/SpringSnow/comments/commentRss/89178.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/SpringSnow/services/trackbacks/89178.html</trackback:ping><description><![CDATA[<p>C++ 对象的内存布局(下)</p>
<p>&nbsp;</p>
<p>陈皓</p>
<p><a href="http://blog.csdn.net/haoel">http://blog.csdn.net/haoel</a></p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>重复继承<br>&nbsp;</p>
<p>下面我们再来看看，发生重复继承的情况。所谓重复继承，也就是某个基类被间接地重复继承了多次。</p>
<p>&nbsp;</p>
<p>下图是一个继承图，我们重载了父类的f()函数。</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>其类继承的源代码如下所示。其中，每个类都有两个变量，一个是整形（4字节），一个是字符（1字节），而且还有自己的虚函数，自己overwrite父类的虚函数。如子类D中，f()覆盖了超类的函数， f1() 和f2() 覆盖了其父类的虚函数，Df()为自己的虚函数。</p>
<p>&nbsp;</p>
<p>class B</p>
<p>{</p>
<p>&nbsp;&nbsp;&nbsp; public:</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; int ib;</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; char cb;</p>
<p>&nbsp;&nbsp;&nbsp; public:</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; B():ib(0),cb('B') {}</p>
<p>&nbsp;</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; virtual void f() { cout &lt;&lt; "B::f()" &lt;&lt; endl;}</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; virtual void Bf() { cout &lt;&lt; "B::Bf()" &lt;&lt; endl;}</p>
<p>};</p>
<p>class B1 :&nbsp; public B</p>
<p>{</p>
<p>&nbsp;&nbsp;&nbsp; public:</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; int ib1;</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; char cb1;</p>
<p>&nbsp;&nbsp;&nbsp; public:</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; B1():ib1(11),cb1('1') {}</p>
<p>&nbsp;</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; virtual void f() { cout &lt;&lt; "B1::f()" &lt;&lt; endl;}</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; virtual void f1() { cout &lt;&lt; "B1::f1()" &lt;&lt; endl;}</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; virtual void Bf1() { cout &lt;&lt; "B1::Bf1()" &lt;&lt; endl;}</p>
<p>&nbsp;</p>
<p>};</p>
<p>class B2:&nbsp; public B</p>
<p>{</p>
<p>&nbsp;&nbsp;&nbsp; public:</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; int ib2;</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; char cb2;</p>
<p>&nbsp;&nbsp;&nbsp; public:</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; B2():ib2(12),cb2('2') {}</p>
<p>&nbsp;</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; virtual void f() { cout &lt;&lt; "B2::f()" &lt;&lt; endl;}</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; virtual void f2() { cout &lt;&lt; "B2::f2()" &lt;&lt; endl;}</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; virtual void Bf2() { cout &lt;&lt; "B2::Bf2()" &lt;&lt; endl;}</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </p>
<p>};</p>
<p>&nbsp;</p>
<p>class D : public B1, public B2</p>
<p>{</p>
<p>&nbsp;&nbsp;&nbsp; public:</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; int id;</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; char cd;</p>
<p>&nbsp;&nbsp;&nbsp; public:</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; D():id(100),cd('D') {}</p>
<p>&nbsp;</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; virtual void f() { cout &lt;&lt; "D::f()" &lt;&lt; endl;}</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; virtual void f1() { cout &lt;&lt; "D::f1()" &lt;&lt; endl;}</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; virtual void f2() { cout &lt;&lt; "D::f2()" &lt;&lt; endl;}</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; virtual void Df() { cout &lt;&lt; "D::Df()" &lt;&lt; endl;}</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </p>
<p>};</p>
<p>我们用来存取子类内存布局的代码如下所示：（在VC++ 2003和G++ 3.4.4下）</p>
<p>&nbsp;&nbsp;&nbsp; typedef void(*Fun)(void);</p>
<p>&nbsp;&nbsp;&nbsp; int** pVtab = NULL;</p>
<p>&nbsp;&nbsp;&nbsp; Fun pFun = NULL;</p>
<p>&nbsp;</p>
<p>&nbsp;&nbsp;&nbsp; D d;</p>
<p>&nbsp;&nbsp;&nbsp; pVtab = (int**)&amp;d;</p>
<p>&nbsp;&nbsp;&nbsp; cout &lt;&lt; "[0] D::B1::_vptr-&gt;" &lt;&lt; endl;</p>
<p>&nbsp;&nbsp;&nbsp; pFun = (Fun)pVtab[0][0];</p>
<p>&nbsp;&nbsp;&nbsp; cout &lt;&lt; "&nbsp;&nbsp;&nbsp;&nbsp; [0] ";&nbsp;&nbsp;&nbsp; pFun();</p>
<p>&nbsp;&nbsp;&nbsp; pFun = (Fun)pVtab[0][1];</p>
<p>&nbsp;&nbsp;&nbsp; cout &lt;&lt; "&nbsp;&nbsp;&nbsp;&nbsp; [1] ";&nbsp;&nbsp;&nbsp; pFun();</p>
<p>&nbsp;&nbsp;&nbsp; pFun = (Fun)pVtab[0][2];</p>
<p>&nbsp;&nbsp;&nbsp; cout &lt;&lt; "&nbsp;&nbsp;&nbsp;&nbsp; [2] ";&nbsp;&nbsp;&nbsp; pFun();</p>
<p>&nbsp;&nbsp;&nbsp; pFun = (Fun)pVtab[0][3];</p>
<p>&nbsp;&nbsp;&nbsp; cout &lt;&lt; "&nbsp;&nbsp;&nbsp;&nbsp; [3] ";&nbsp;&nbsp;&nbsp; pFun();</p>
<p>&nbsp;&nbsp;&nbsp; pFun = (Fun)pVtab[0][4];</p>
<p>&nbsp;&nbsp;&nbsp; cout &lt;&lt; "&nbsp;&nbsp;&nbsp;&nbsp; [4] ";&nbsp;&nbsp;&nbsp; pFun();</p>
<p>&nbsp;&nbsp;&nbsp; pFun = (Fun)pVtab[0][5];</p>
<p>&nbsp;&nbsp;&nbsp; cout &lt;&lt; "&nbsp;&nbsp;&nbsp;&nbsp; [5] 0x" &lt;&lt; pFun &lt;&lt; endl;</p>
<p>&nbsp;&nbsp;&nbsp; </p>
<p>&nbsp;&nbsp;&nbsp; cout &lt;&lt; "[1] B::ib = " &lt;&lt; (int)pVtab[1] &lt;&lt; endl;</p>
<p>&nbsp;&nbsp;&nbsp; cout &lt;&lt; "[2] B::cb = " &lt;&lt; (char)pVtab[2] &lt;&lt; endl;</p>
<p>&nbsp;&nbsp;&nbsp; cout &lt;&lt; "[3] B1::ib1 = " &lt;&lt; (int)pVtab[3] &lt;&lt; endl;</p>
<p>&nbsp;&nbsp;&nbsp; cout &lt;&lt; "[4] B1::cb1 = " &lt;&lt; (char)pVtab[4] &lt;&lt; endl;</p>
<p>&nbsp;</p>
<p>&nbsp;&nbsp;&nbsp; cout &lt;&lt; "[5] D::B2::_vptr-&gt;" &lt;&lt; endl;</p>
<p>&nbsp;&nbsp;&nbsp; pFun = (Fun)pVtab[5][0];</p>
<p>&nbsp;&nbsp;&nbsp; cout &lt;&lt; "&nbsp;&nbsp;&nbsp;&nbsp; [0] ";&nbsp;&nbsp;&nbsp; pFun();</p>
<p>&nbsp;&nbsp;&nbsp; pFun = (Fun)pVtab[5][1];</p>
<p>&nbsp;&nbsp;&nbsp; cout &lt;&lt; "&nbsp;&nbsp;&nbsp;&nbsp; [1] ";&nbsp;&nbsp;&nbsp; pFun();</p>
<p>&nbsp;&nbsp;&nbsp; pFun = (Fun)pVtab[5][2];</p>
<p>&nbsp;&nbsp;&nbsp; cout &lt;&lt; "&nbsp;&nbsp;&nbsp;&nbsp; [2] ";&nbsp;&nbsp;&nbsp; pFun();</p>
<p>&nbsp;&nbsp;&nbsp; pFun = (Fun)pVtab[5][3];</p>
<p>&nbsp;&nbsp;&nbsp; cout &lt;&lt; "&nbsp;&nbsp;&nbsp;&nbsp; [3] ";&nbsp;&nbsp;&nbsp; pFun();</p>
<p>&nbsp;&nbsp;&nbsp; pFun = (Fun)pVtab[5][4];</p>
<p>&nbsp;&nbsp;&nbsp; cout &lt;&lt; "&nbsp;&nbsp;&nbsp;&nbsp; [4] 0x" &lt;&lt; pFun &lt;&lt; endl;</p>
<p>&nbsp;</p>
<p>&nbsp;&nbsp;&nbsp; cout &lt;&lt; "[6] B::ib = " &lt;&lt; (int)pVtab[6] &lt;&lt; endl;</p>
<p>&nbsp;&nbsp;&nbsp; cout &lt;&lt; "[7] B::cb = " &lt;&lt; (char)pVtab[7] &lt;&lt; endl;&nbsp;&nbsp;&nbsp; </p>
<p>&nbsp;&nbsp;&nbsp; cout &lt;&lt; "[8] B2::ib2 = " &lt;&lt; (int)pVtab[8] &lt;&lt; endl;</p>
<p>&nbsp;&nbsp;&nbsp; cout &lt;&lt; "[9] B2::cb2 = " &lt;&lt; (char)pVtab[9] &lt;&lt; endl;</p>
<p>&nbsp;</p>
<p>&nbsp;&nbsp;&nbsp; cout &lt;&lt; "[10] D::id = " &lt;&lt; (int)pVtab[10] &lt;&lt; endl;</p>
<p>&nbsp;&nbsp;&nbsp; cout &lt;&lt; "[11] D::cd = " &lt;&lt; (char)pVtab[11] &lt;&lt; endl;</p>
<p>&nbsp;</p>
<p>程序运行结果如下：</p>
<p><br>&nbsp;</p>
<p>GCC 3.4.4<br>&nbsp;VC++ 2003<br>&nbsp;<br>[0] D::B1::_vptr-&gt;</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp; [0] D::f()</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp; [1] B::Bf()</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp; [2] D::f1()</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp; [3] B1::Bf1()</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp; [4] D::f2()</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp; [5] 0x1</p>
<p>[1] B::ib = 0</p>
<p>[2] B::cb = B</p>
<p>[3] B1::ib1 = 11</p>
<p>[4] B1::cb1 = 1</p>
<p>[5] D::B2::_vptr-&gt;</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp; [0] D::f()</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp; [1] B::Bf()</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp; [2] D::f2()</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp; [3] B2::Bf2()</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp; [4] 0x0</p>
<p>[6] B::ib = 0</p>
<p>[7] B::cb = B</p>
<p>[8] B2::ib2 = 12</p>
<p>[9] B2::cb2 = 2</p>
<p>[10] D::id = 100</p>
<p>[11] D::cd = D<br>&nbsp;[0] D::B1::_vptr-&gt;</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp; [0] D::f()</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp; [1] B::Bf()</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp; [2] D::f1()</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp; [3] B1::Bf1()</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp; [4] D::Df()</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp; [5] 0x00000000</p>
<p>[1] B::ib = 0</p>
<p>[2] B::cb = B</p>
<p>[3] B1::ib1 = 11</p>
<p>[4] B1::cb1 = 1</p>
<p>[5] D::B2::_vptr-&gt;</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp; [0] D::f()</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp; [1] B::Bf()</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp; [2] D::f2()</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp; [3] B2::Bf2()</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp; [4] 0x00000000</p>
<p>[6] B::ib = 0</p>
<p>[7] B::cb = B</p>
<p>[8] B2::ib2 = 12</p>
<p>[9] B2::cb2 = 2</p>
<p>[10] D::id = 100</p>
<p>[11] D::cd = D<br>&nbsp;</p>
<p><br>&nbsp;</p>
<p>下面是对于子类实例中的虚函数表的图：</p>
<p>&nbsp;</p>
<p><br>&nbsp;</p>
<p>&nbsp;</p>
<p><br>我们可以看见，最顶端的父类B其成员变量存在于B1和B2中，并被D给继承下去了。而在D中，其有B1和B2的实例，于是B的成员在D的实例中存在两份，一份是B1继承而来的，另一份是B2继承而来的。所以，如果我们使用以下语句，则会产生二义性编译错误：</p>
<p>&nbsp;</p>
<p>D d;</p>
<p>d.ib = 0;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //二义性错误</p>
<p>d.B1::ib = 1;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //正确</p>
<p>d.B2::ib = 2;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //正确</p>
<p><br>注意，上面例程中的最后两条语句存取的是两个变量。虽然我们消除了二义性的编译错误，但B类在D中还是有两个实例，这种继承造成了数据的重复，我们叫这种继承为重复继承。重复的基类数据成员可能并不是我们想要的。所以，C++引入了虚基类的概念。</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>钻石型多重虚拟继承<br>&nbsp;</p>
<p>虚拟继承的出现就是为了解决重复继承中多个间接父类的问题的。钻石型的结构是其最经典的结构。也是我们在这里要讨论的结构：</p>
<p>&nbsp;</p>
<p>上述的&#8220;重复继承&#8221;只需要把B1和B2继承B的语法中加上virtual 关键，就成了虚拟继承，其继承图如下所示：</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>上图和前面的&#8220;重复继承&#8221;中的类的内部数据和接口都是完全一样的，只是我们采用了虚拟继承：其省略后的源码如下所示：</p>
<p>&nbsp;</p>
<p>class B {&#8230;&#8230;};</p>
<p>class B1 : virtual public B{&#8230;&#8230;};</p>
<p>class B2: virtual public B{&#8230;&#8230;};</p>
<p>class D : public B1, public B2{ &#8230;&#8230; };</p>
<p>&nbsp;</p>
<p>在查看D之前，我们先看一看单一虚拟继承的情况。下面是一段在VC++2003下的测试程序：（因为VC++和GCC的内存而局上有一些细节上的不同，所以这里只给出VC++的程序，GCC下的程序大家可以根据我给出的程序自己仿照着写一个去试一试）：</p>
<p>&nbsp;</p>
<p>&nbsp;&nbsp;&nbsp; int** pVtab = NULL;</p>
<p>&nbsp;&nbsp;&nbsp; Fun pFun = NULL;</p>
<p>&nbsp;</p>
<p>&nbsp;&nbsp;&nbsp; B1 bb1;</p>
<p>&nbsp;</p>
<p>&nbsp;&nbsp;&nbsp; pVtab = (int**)&amp;bb1;</p>
<p>&nbsp;&nbsp;&nbsp; cout &lt;&lt; "[0] B1::_vptr-&gt;" &lt;&lt; endl;</p>
<p>&nbsp;&nbsp;&nbsp; pFun = (Fun)pVtab[0][0];</p>
<p>&nbsp;&nbsp;&nbsp; cout &lt;&lt; "&nbsp;&nbsp;&nbsp;&nbsp; [0] ";</p>
<p>&nbsp;&nbsp;&nbsp; pFun(); //B1::f1();</p>
<p>&nbsp;&nbsp;&nbsp; cout &lt;&lt; "&nbsp;&nbsp;&nbsp;&nbsp; [1] ";</p>
<p>&nbsp;&nbsp;&nbsp; pFun = (Fun)pVtab[0][1];</p>
<p>&nbsp;&nbsp;&nbsp; pFun(); //B1::bf1();</p>
<p>&nbsp;&nbsp;&nbsp; cout &lt;&lt; "&nbsp;&nbsp;&nbsp;&nbsp; [2] ";</p>
<p>&nbsp;&nbsp;&nbsp; cout &lt;&lt; pVtab[0][2] &lt;&lt; endl;</p>
<p>&nbsp;</p>
<p>&nbsp;&nbsp;&nbsp; cout &lt;&lt; "[1] = 0x";</p>
<p>&nbsp;&nbsp;&nbsp; cout &lt;&lt; (int*)*((int*)(&amp;bb1)+1) &lt;&lt;endl; //B1::ib1</p>
<p>&nbsp;&nbsp;&nbsp; cout &lt;&lt; "[2] B1::ib1 = ";</p>
<p>&nbsp;&nbsp;&nbsp; cout &lt;&lt; (int)*((int*)(&amp;bb1)+2) &lt;&lt;endl; //B1::ib1</p>
<p>&nbsp;&nbsp;&nbsp; cout &lt;&lt; "[3] B1::cb1 = ";</p>
<p>&nbsp;&nbsp;&nbsp; cout &lt;&lt; (char)*((int*)(&amp;bb1)+3) &lt;&lt; endl; //B1::cb1</p>
<p>&nbsp;</p>
<p>&nbsp;&nbsp;&nbsp; cout &lt;&lt; "[4] = 0x";</p>
<p>&nbsp;&nbsp;&nbsp; cout &lt;&lt; (int*)*((int*)(&amp;bb1)+4) &lt;&lt; endl; //NULL</p>
<p>&nbsp;</p>
<p>&nbsp;&nbsp;&nbsp; cout &lt;&lt; "[5] B::_vptr-&gt;" &lt;&lt; endl;</p>
<p>&nbsp;&nbsp;&nbsp; pFun = (Fun)pVtab[5][0];</p>
<p>&nbsp;&nbsp;&nbsp; cout &lt;&lt; "&nbsp;&nbsp;&nbsp;&nbsp; [0] ";</p>
<p>&nbsp;&nbsp;&nbsp; pFun(); //B1::f();</p>
<p>&nbsp;&nbsp;&nbsp; pFun = (Fun)pVtab[5][1];</p>
<p>&nbsp;&nbsp;&nbsp; cout &lt;&lt; "&nbsp;&nbsp;&nbsp;&nbsp; [1] ";</p>
<p>&nbsp;&nbsp;&nbsp; pFun(); //B::Bf();</p>
<p>&nbsp;&nbsp;&nbsp; cout &lt;&lt; "&nbsp;&nbsp;&nbsp;&nbsp; [2] ";</p>
<p>&nbsp;&nbsp;&nbsp; cout &lt;&lt; "0x" &lt;&lt; (Fun)pVtab[5][2] &lt;&lt; endl;</p>
<p>&nbsp;</p>
<p>&nbsp;&nbsp;&nbsp; cout &lt;&lt; "[6] B::ib = ";</p>
<p>&nbsp;&nbsp;&nbsp; cout &lt;&lt; (int)*((int*)(&amp;bb1)+6) &lt;&lt;endl; //B::ib</p>
<p>&nbsp;&nbsp;&nbsp; cout &lt;&lt; "[7] B::cb = ";</p>
<p>&nbsp;</p>
<p>其运行结果如下（我结出了GCC的和VC++2003的对比）：</p>
<p>&nbsp;</p>
<p>GCC 3.4.4<br>&nbsp;VC++ 2003<br>&nbsp;<br>[0] B1::_vptr -&gt;</p>
<p>&nbsp;&nbsp;&nbsp; [0] : B1::f()</p>
<p>&nbsp;&nbsp;&nbsp; [1] : B1::f1()</p>
<p>&nbsp;&nbsp;&nbsp; [2] : B1::Bf1()</p>
<p>&nbsp;&nbsp;&nbsp; [3] : 0</p>
<p>[1] B1::ib1 : 11</p>
<p>[2] B1::cb1 : 1</p>
<p>[3] B::_vptr -&gt;</p>
<p>&nbsp;&nbsp;&nbsp; [0] : B1::f()</p>
<p>&nbsp;&nbsp;&nbsp; [1] : B::Bf()</p>
<p>&nbsp;&nbsp;&nbsp; [2] : 0</p>
<p>[4] B::ib : 0</p>
<p>[5] B::cb : B</p>
<p>[6] NULL : 0<br>&nbsp;[0] B1::_vptr-&gt;</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp; [0] B1::f1()</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp; [1] B1::Bf1()</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp; [2] 0</p>
<p>[1] = 0x00454310 &#231;该地址取值后是-4</p>
<p>[2] B1::ib1 = 11</p>
<p>[3] B1::cb1 = 1</p>
<p>[4] = 0x00000000</p>
<p>[5] B::_vptr-&gt;</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp; [0] B1::f()</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp; [1] B::Bf()</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp; [2] 0x00000000</p>
<p>[6] B::ib = 0</p>
<p>[7] B::cb = B<br>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>这里，大家可以自己对比一下。关于细节上，我会在后面一并再说。</p>
<p>&nbsp;</p>
<p>下面的测试程序是看子类D的内存布局，同样是VC++ 2003的（因为VC++和GCC的内存布局上有一些细节上的不同，而VC++的相对要清楚很多，所以这里只给出VC++的程序，GCC下的程序大家可以根据我给出的程序自己仿照着写一个去试一试）：</p>
<p>&nbsp;</p>
<p>&nbsp;&nbsp;&nbsp; D d;</p>
<p>&nbsp;</p>
<p>&nbsp;&nbsp;&nbsp; pVtab = (int**)&amp;d;</p>
<p>&nbsp;&nbsp;&nbsp; cout &lt;&lt; "[0] D::B1::_vptr-&gt;" &lt;&lt; endl;</p>
<p>&nbsp;&nbsp;&nbsp; pFun = (Fun)pVtab[0][0];</p>
<p>&nbsp;&nbsp;&nbsp; cout &lt;&lt; "&nbsp;&nbsp;&nbsp;&nbsp; [0] ";&nbsp;&nbsp;&nbsp; pFun(); //D::f1();</p>
<p>&nbsp;&nbsp;&nbsp; pFun = (Fun)pVtab[0][1];</p>
<p>&nbsp;&nbsp;&nbsp; cout &lt;&lt; "&nbsp;&nbsp;&nbsp;&nbsp; [1] ";&nbsp;&nbsp;&nbsp; pFun(); //B1::Bf1();</p>
<p>&nbsp;&nbsp;&nbsp; pFun = (Fun)pVtab[0][2];</p>
<p>&nbsp;&nbsp;&nbsp; cout &lt;&lt; "&nbsp;&nbsp;&nbsp;&nbsp; [2] ";&nbsp;&nbsp;&nbsp; pFun(); //D::Df();</p>
<p>&nbsp;&nbsp;&nbsp; pFun = (Fun)pVtab[0][3];</p>
<p>&nbsp;&nbsp;&nbsp; cout &lt;&lt; "&nbsp;&nbsp;&nbsp;&nbsp; [3] ";</p>
<p>&nbsp;&nbsp;&nbsp; cout &lt;&lt; pFun &lt;&lt; endl;</p>
<p>&nbsp;</p>
<p>&nbsp;&nbsp;&nbsp; //cout &lt;&lt; pVtab[4][2] &lt;&lt; endl;</p>
<p>&nbsp;&nbsp;&nbsp; cout &lt;&lt; "[1] = 0x";</p>
<p>&nbsp;&nbsp;&nbsp; cout &lt;&lt;&nbsp; (int*)((&amp;dd)+1) &lt;&lt;endl; //????</p>
<p>&nbsp;</p>
<p>&nbsp;&nbsp;&nbsp; cout &lt;&lt; "[2] B1::ib1 = ";</p>
<p>&nbsp;&nbsp;&nbsp; cout &lt;&lt; *((int*)(&amp;dd)+2) &lt;&lt;endl; //B1::ib1</p>
<p>&nbsp;&nbsp;&nbsp; cout &lt;&lt; "[3] B1::cb1 = ";</p>
<p>&nbsp;&nbsp;&nbsp; cout &lt;&lt; (char)*((int*)(&amp;dd)+3) &lt;&lt; endl; //B1::cb1</p>
<p>&nbsp;</p>
<p>&nbsp;&nbsp;&nbsp; //---------------------</p>
<p>&nbsp;&nbsp;&nbsp; cout &lt;&lt; "[4] D::B2::_vptr-&gt;" &lt;&lt; endl;</p>
<p>&nbsp;&nbsp;&nbsp; pFun = (Fun)pVtab[4][0];</p>
<p>&nbsp;&nbsp;&nbsp; cout &lt;&lt; "&nbsp;&nbsp;&nbsp;&nbsp; [0] ";&nbsp;&nbsp;&nbsp; pFun(); //D::f2();</p>
<p>&nbsp;&nbsp;&nbsp; pFun = (Fun)pVtab[4][1];</p>
<p>&nbsp;&nbsp;&nbsp; cout &lt;&lt; "&nbsp;&nbsp;&nbsp;&nbsp; [1] ";&nbsp;&nbsp;&nbsp; pFun(); //B2::Bf2();</p>
<p>&nbsp;&nbsp;&nbsp; pFun = (Fun)pVtab[4][2];</p>
<p>&nbsp;&nbsp;&nbsp; cout &lt;&lt; "&nbsp;&nbsp;&nbsp;&nbsp; [2] ";</p>
<p>&nbsp;&nbsp;&nbsp; cout &lt;&lt; pFun &lt;&lt; endl;</p>
<p>&nbsp;&nbsp;&nbsp; </p>
<p>&nbsp;&nbsp;&nbsp; cout &lt;&lt; "[5] = 0x";</p>
<p>&nbsp;&nbsp;&nbsp; cout &lt;&lt; *((int*)(&amp;dd)+5) &lt;&lt; endl; // ???</p>
<p>&nbsp;</p>
<p>&nbsp;&nbsp;&nbsp; cout &lt;&lt; "[6] B2::ib2 = ";</p>
<p>&nbsp;&nbsp;&nbsp; cout &lt;&lt; (int)*((int*)(&amp;dd)+6) &lt;&lt;endl; //B2::ib2</p>
<p>&nbsp;&nbsp;&nbsp; cout &lt;&lt; "[7] B2::cb2 = ";</p>
<p>&nbsp;&nbsp;&nbsp; cout &lt;&lt; (char)*((int*)(&amp;dd)+7) &lt;&lt; endl; //B2::cb2</p>
<p>&nbsp;</p>
<p>&nbsp;&nbsp;&nbsp; cout &lt;&lt; "[8] D::id = ";</p>
<p>&nbsp;&nbsp;&nbsp; cout &lt;&lt; *((int*)(&amp;dd)+8) &lt;&lt; endl; //D::id</p>
<p>&nbsp;&nbsp;&nbsp; cout &lt;&lt; "[9] D::cd = ";</p>
<p>&nbsp;&nbsp;&nbsp; cout &lt;&lt; (char)*((int*)(&amp;dd)+9) &lt;&lt; endl;//D::cd</p>
<p>&nbsp;</p>
<p>&nbsp;&nbsp;&nbsp; cout &lt;&lt; "[10]&nbsp; = 0x";</p>
<p>&nbsp;&nbsp;&nbsp; cout &lt;&lt; (int*)*((int*)(&amp;dd)+10) &lt;&lt; endl;</p>
<p>&nbsp;&nbsp;&nbsp; //---------------------</p>
<p>&nbsp;&nbsp;&nbsp; cout &lt;&lt; "[11] D::B::_vptr-&gt;" &lt;&lt; endl;</p>
<p>&nbsp;&nbsp;&nbsp; pFun = (Fun)pVtab[11][0];</p>
<p>&nbsp;&nbsp;&nbsp; cout &lt;&lt; "&nbsp;&nbsp;&nbsp;&nbsp; [0] ";&nbsp;&nbsp;&nbsp; pFun(); //D::f();</p>
<p>&nbsp;&nbsp;&nbsp; pFun = (Fun)pVtab[11][1];</p>
<p>&nbsp;&nbsp;&nbsp; cout &lt;&lt; "&nbsp;&nbsp;&nbsp;&nbsp; [1] ";&nbsp;&nbsp;&nbsp; pFun(); //B::Bf();</p>
<p>&nbsp;&nbsp;&nbsp; pFun = (Fun)pVtab[11][2];</p>
<p>&nbsp;&nbsp;&nbsp; cout &lt;&lt; "&nbsp;&nbsp;&nbsp;&nbsp; [2] ";</p>
<p>&nbsp;&nbsp;&nbsp; cout &lt;&lt; pFun &lt;&lt; endl;</p>
<p>&nbsp;</p>
<p>&nbsp;&nbsp;&nbsp; cout &lt;&lt; "[12] B::ib = ";</p>
<p>&nbsp;&nbsp;&nbsp; cout &lt;&lt; *((int*)(&amp;dd)+12) &lt;&lt; endl; //B::ib</p>
<p>&nbsp;&nbsp;&nbsp; cout &lt;&lt; "[13] B::cb = ";</p>
<p>&nbsp;&nbsp;&nbsp; cout &lt;&lt; (char)*((int*)(&amp;dd)+13) &lt;&lt;endl;//B::cb</p>
<p>&nbsp;</p>
<p>下面给出运行后的结果（分VC++和GCC两部份）</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>GCC 3.4.4<br>&nbsp;VC++ 2003<br>&nbsp;<br>[0] B1::_vptr -&gt;</p>
<p>&nbsp;&nbsp;&nbsp; [0] : D::f()</p>
<p>&nbsp;&nbsp;&nbsp; [1] : D::f1()</p>
<p>&nbsp;&nbsp;&nbsp; [2] : B1::Bf1()</p>
<p>&nbsp;&nbsp;&nbsp; [3] : D::f2()</p>
<p>&nbsp;&nbsp;&nbsp; [4] : D::Df()</p>
<p>&nbsp;&nbsp;&nbsp; [5] : 1</p>
<p>[1] B1::ib1 : 11</p>
<p>[2] B1::cb1 : 1</p>
<p>[3] B2::_vptr -&gt;</p>
<p>&nbsp;&nbsp;&nbsp; [0] : D::f()</p>
<p>&nbsp;&nbsp;&nbsp; [1] : D::f2()</p>
<p>&nbsp;&nbsp;&nbsp; [2] : B2::Bf2()</p>
<p>&nbsp;&nbsp;&nbsp; [3] : 0</p>
<p>[4] B2::ib2 : 12</p>
<p>[5] B2::cb2 : 2</p>
<p>[6] D::id : 100</p>
<p>[7] D::cd : D</p>
<p>[8] B::_vptr -&gt;</p>
<p>&nbsp;&nbsp;&nbsp; [0] : D::f()</p>
<p>&nbsp;&nbsp;&nbsp; [1] : B::Bf()</p>
<p>&nbsp;&nbsp;&nbsp; [2] : 0</p>
<p>[9] B::ib : 0</p>
<p>[10] B::cb : B</p>
<p>[11] NULL : 0<br>&nbsp;[0] D::B1::_vptr-&gt;</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp; [0] D::f1()</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp; [1] B1::Bf1()</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp; [2] D::Df()</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp; [3] 00000000</p>
<p>[1] = 0x0013FDC4&nbsp; &#231; 该地址取值后是-4</p>
<p>[2] B1::ib1 = 11</p>
<p>[3] B1::cb1 = 1</p>
<p>[4] D::B2::_vptr-&gt;</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp; [0] D::f2()</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp; [1] B2::Bf2()</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp; [2] 00000000</p>
<p>[5] = 0x4539260&nbsp;&nbsp; &#231; 该地址取值后是-4</p>
<p>[6] B2::ib2 = 12</p>
<p>[7] B2::cb2 = 2</p>
<p>[8] D::id = 100</p>
<p>[9] D::cd = D</p>
<p>[10]&nbsp; = 0x00000000</p>
<p>[11] D::B::_vptr-&gt;</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp; [0] D::f()</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp; [1] B::Bf()</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp; [2] 00000000</p>
<p>[12] B::ib = 0</p>
<p>[13] B::cb = B<br>&nbsp;</p>
<p><br>&nbsp;</p>
<p>&nbsp;</p>
<p>关于虚拟继承的运行结果我就不画图了（前面的作图已经让我产生了很严重的厌倦感，所以就偷个懒了，大家见谅了）</p>
<p>&nbsp;</p>
<p>在上面的输出结果中，我用不同的颜色做了一些标明。我们可以看到如下的几点：</p>
<p>&nbsp;</p>
<p>1）无论是GCC还是VC++，除了一些细节上的不同，其大体上的对象布局是一样的。也就是说，先是B1（黄色），然后是B2（绿色），接着是D（灰色），而B这个超类（青蓝色）的实例都放在最后的位置。</p>
<p>&nbsp;</p>
<p>2）关于虚函数表，尤其是第一个虚表，GCC和VC++有很重大的不一样。但仔细看下来，还是VC++的虚表比较清晰和有逻辑性。</p>
<p>&nbsp;</p>
<p>3）VC++和GCC都把B这个超类放到了最后，而VC++有一个NULL分隔符把B和B1和B2的布局分开。GCC则没有。</p>
<p>&nbsp;</p>
<p>4）VC++中的内存布局有两个地址我有些不是很明白，在其中我用红色标出了。取其内容是-4。接道理来说，这个指针应该是指向B类实例的内存地址（这个做法就是为了保证重复的父类只有一个实例的技术）。但取值后却不是。这点我目前还并不太清楚，还向大家请教。</p>
<p>&nbsp;</p>
<p>5）GCC的内存布局中在B1和B2中则没有指向B的指针。这点可以理解，编译器可以通过计算B1和B2的size而得出B的偏移量。</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>结束语<br>C++这门语言是一门比较复杂的语言，对于程序员来说，我们似乎永远摸不清楚这门语言背着我们在干了什么。需要熟悉这门语言，我们就必需要了解C++里面的那些东西，需要我们去了解他后面的内存对象。这样我们才能真正的了解C++，从而能够更好的使用C++这门最难的编程语言。</p>
<p>&nbsp;</p>
<p>在文章束之前还是介绍一下自己吧。我从事软件研发有十个年头了，目前是软件开发技术主管，技术方面，主攻Unix/C/C++，比较喜欢网络上的技术，比如分布式计算，网格计算，P2P，Ajax等一切和互联网相关的东西。管理方面比较擅长于团队建设，技术趋势分析，项目管理。欢迎大家和我交流，我的MSN和Email是：<a href="&#109;&#97;&#105;&#108;&#116;&#111;&#58;&#104;&#97;&#111;&#101;&#108;&#64;&#104;&#111;&#116;&#109;&#97;&#105;&#108;">haoel@hotmail</a>.</p>
<p><br>本文来自CSDN博客，转载请标明出处：<a href="http://blog.csdn.net/haoel/archive/2008/10/15/3081385.aspx">http://blog.csdn.net/haoel/archive/2008/10/15/3081385.aspx</a></p>
<img src ="http://www.cppblog.com/SpringSnow/aggbug/89178.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/SpringSnow/" target="_blank">Sandy</a> 2009-07-03 17:21 <a href="http://www.cppblog.com/SpringSnow/articles/89178.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>转: C++ 对象的内存布局(上)</title><link>http://www.cppblog.com/SpringSnow/articles/89177.html</link><dc:creator>Sandy</dc:creator><author>Sandy</author><pubDate>Fri, 03 Jul 2009 09:20:00 GMT</pubDate><guid>http://www.cppblog.com/SpringSnow/articles/89177.html</guid><wfw:comment>http://www.cppblog.com/SpringSnow/comments/89177.html</wfw:comment><comments>http://www.cppblog.com/SpringSnow/articles/89177.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/SpringSnow/comments/commentRss/89177.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/SpringSnow/services/trackbacks/89177.html</trackback:ping><description><![CDATA[<p>好文章,就怕找不到了,先转到这里,好以后满满品读.<br><br>C++ 对象的内存布局(上)</p>
<p>&nbsp;</p>
<p>陈皓</p>
<p><a href="http://blog.csdn.net/haoel">http://blog.csdn.net/haoel</a></p>
<p>&nbsp;</p>
<p>前言<br>&nbsp;</p>
<p>07年12月，我写了一篇《C++虚函数表解析》的文章，引起了大家的兴趣。有很多朋友对我的文章留了言，有鼓励我的，有批评我的，还有很多问问题的。我在这里一并对大家的留言表示感谢。这也是我为什么再写一篇续言的原因。因为，在上一篇文章中，我用了的示例都是非常简单的，主要是为了说明一些机理上的问题，也是为了图一些表达上方便和简单。不想，这篇文章成为了打开C++对象模型内存布局的一个引子，引发了大家对C++对象的更深层次的讨论。当然，我之前的文章还有很多方面没有涉及，从我个人感觉下来，在谈论虚函数表里，至少有以下这些内容没有涉及：</p>
<p>&nbsp;</p>
<p>1）有成员变量的情况。</p>
<p>2）有重复继承的情况。</p>
<p>3）有虚拟继承的情况。</p>
<p>4）有钻石型虚拟继承的情况。</p>
<p>&nbsp;</p>
<p>这些都是我本篇文章需要向大家说明的东西。所以，这篇文章将会是《C++虚函数表解析》的一个续篇，也是一篇高级进阶的文章。我希望大家在读这篇文章之前对C++有一定的基础和了解，并能先读我的上一篇文章。因为这篇文章的深度可能会比较深，而且会比较杂乱，我希望你在读本篇文章时不会有大脑思维紊乱导致大脑死机的情况。;-)</p>
<p>&nbsp;</p>
<p>对象的影响因素<br>&nbsp;</p>
<p>简而言之，我们一个类可能会有如下的影响因素：</p>
<p>&nbsp;</p>
<p>1）成员变量</p>
<p>2）虚函数（产生虚函数表）</p>
<p>3）单一继承（只继承于一个类）</p>
<p>4）多重继承（继承多个类）</p>
<p>5）重复继承（继承的多个父类中其父类有相同的超类）</p>
<p>6）虚拟继承（使用virtual方式继承，为了保证继承后父类的内存布局只会存在一份）</p>
<p>上述的东西通常是C++这门语言在语义方面对对象内部的影响因素，当然，还会有编译器的影响（比如优化），还有字节对齐的影响。在这里我们都不讨论，我们只讨论C++语言上的影响。</p>
<p>&nbsp;</p>
<p>本篇文章着重讨论下述几个情况下的C++对象的内存布局情况。</p>
<p>&nbsp;</p>
<p>1）单一的一般继承（带成员变量、虚函数、虚函数覆盖）</p>
<p>2）单一的虚拟继承（带成员变量、虚函数、虚函数覆盖）</p>
<p>3）多重继承（带成员变量、虚函数、虚函数覆盖）</p>
<p>4）重复多重继承（带成员变量、虚函数、虚函数覆盖）</p>
<p>5）钻石型的虚拟多重继承（带成员变量、虚函数、虚函数覆盖）</p>
<p>&nbsp;</p>
<p>我们的目标就是，让事情越来越复杂。</p>
<p>&nbsp;</p>
<p>知识复习<br>&nbsp;</p>
<p>我们简单地复习一下，我们可以通过对象的地址来取得虚函数表的地址，如：</p>
<p>&nbsp;</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; typedef void(*Fun)(void);</p>
<p>&nbsp;</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Base b;</p>
<p>&nbsp;</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Fun pFun = NULL;</p>
<p>&nbsp;</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; cout &lt;&lt; "虚函数表地址：" &lt;&lt; (int*)(&amp;b) &lt;&lt; endl;</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; cout &lt;&lt; "虚函数表 — 第一个函数地址：" &lt;&lt; (int*)*(int*)(&amp;b) &lt;&lt; endl;</p>
<p>&nbsp;</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // Invoke the first virtual function&nbsp; </p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; pFun = (Fun)*((int*)*(int*)(&amp;b));</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; pFun();</p>
<p>&nbsp;</p>
<p>我们同样可以用这种方式来取得整个对象实例的内存布局。因为这些东西在内存中都是连续分布的，我们只需要使用适当的地址偏移量，我们就可以获得整个内存对象的布局。</p>
<p>&nbsp;</p>
<p>本篇文章中的例程或内存布局主要使用如下编译器和系统：</p>
<p>1）Windows XP 和 VC++ 2003</p>
<p>2）Cygwin 和 G++ 3.4.4</p>
<p>&nbsp;</p>
<p>单一的一般继承<br>&nbsp;</p>
<p>下面，我们假设有如下所示的一个继承关系：</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p><br>&nbsp;</p>
<p>请注意，在这个继承关系中，父类，子类，孙子类都有自己的一个成员变量。而了类覆盖了父类的f()方法，孙子类覆盖了子类的g_child()及其超类的f()。</p>
<p>&nbsp;</p>
<p>我们的源程序如下所示：</p>
<p>&nbsp;</p>
<p>class Parent {</p>
<p>public:</p>
<p>&nbsp;&nbsp;&nbsp; int iparent;</p>
<p>&nbsp;&nbsp;&nbsp; Parent ():iparent (10) {}</p>
<p>&nbsp;&nbsp;&nbsp; virtual void f() { cout &lt;&lt; " Parent::f()" &lt;&lt; endl; }</p>
<p>&nbsp;&nbsp;&nbsp; virtual void g() { cout &lt;&lt; " Parent::g()" &lt;&lt; endl; }</p>
<p>&nbsp;&nbsp;&nbsp; virtual void h() { cout &lt;&lt; " Parent::h()" &lt;&lt; endl; }</p>
<p>&nbsp;</p>
<p>};</p>
<p>&nbsp;</p>
<p>class Child : public Parent {</p>
<p>public:</p>
<p>&nbsp;&nbsp;&nbsp; int ichild;</p>
<p>&nbsp;&nbsp;&nbsp; Child():ichild(100) {}</p>
<p>&nbsp;&nbsp;&nbsp; virtual void f() { cout &lt;&lt; "Child::f()" &lt;&lt; endl; }</p>
<p>&nbsp;&nbsp;&nbsp; virtual void g_child() { cout &lt;&lt; "Child::g_child()" &lt;&lt; endl; }</p>
<p>&nbsp;&nbsp;&nbsp; virtual void h_child() { cout &lt;&lt; "Child::h_child()" &lt;&lt; endl; }</p>
<p>};</p>
<p>&nbsp;</p>
<p>class GrandChild : public Child{</p>
<p>public:</p>
<p>&nbsp;&nbsp;&nbsp; int igrandchild;</p>
<p>&nbsp;&nbsp;&nbsp; GrandChild():igrandchild(1000) {}</p>
<p>&nbsp;&nbsp;&nbsp; virtual void f() { cout &lt;&lt; "GrandChild::f()" &lt;&lt; endl; }</p>
<p>&nbsp;&nbsp;&nbsp; virtual void g_child() { cout &lt;&lt; "GrandChild::g_child()" &lt;&lt; endl; }</p>
<p>&nbsp;&nbsp;&nbsp; virtual void h_grandchild() { cout &lt;&lt; "GrandChild::h_grandchild()" &lt;&lt; endl; }</p>
<p>};</p>
<p>我们使用以下程序作为测试程序：（下面程序中，我使用了一个int** pVtab 来作为遍历对象内存布局的指针，这样，我就可以方便地像使用数组一样来遍历所有的成员包括其虚函数表了，在后面的程序中，我也是用这样的方法的，请不必感到奇怪，）</p>
<p>&nbsp;</p>
<p>&nbsp;&nbsp;&nbsp; typedef void(*Fun)(void);</p>
<p>&nbsp;</p>
<p>&nbsp;&nbsp;&nbsp; GrandChild gc; </p>
<p>&nbsp;&nbsp;&nbsp; </p>
<p>&nbsp;</p>
<p>&nbsp;&nbsp;&nbsp; int** pVtab = (int**)&amp;gc;</p>
<p>&nbsp;</p>
<p>&nbsp;&nbsp;&nbsp; cout &lt;&lt; "[0] GrandChild::_vptr-&gt;" &lt;&lt; endl;</p>
<p>&nbsp;&nbsp;&nbsp; for (int i=0; (Fun)pVtab[0][i]!=NULL; i++){</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; pFun = (Fun)pVtab[0][i];</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; cout &lt;&lt; "&nbsp;&nbsp;&nbsp; ["&lt;&lt;i&lt;&lt;"] ";</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; pFun();</p>
<p>&nbsp;&nbsp;&nbsp; }</p>
<p>&nbsp;&nbsp;&nbsp; cout &lt;&lt; "[1] Parent.iparent = " &lt;&lt; (int)pVtab[1] &lt;&lt; endl;</p>
<p>&nbsp;&nbsp;&nbsp; cout &lt;&lt; "[2] Child.ichild = " &lt;&lt; (int)pVtab[2] &lt;&lt; endl;</p>
<p>&nbsp;&nbsp;&nbsp; cout &lt;&lt; "[3] GrandChild.igrandchild = " &lt;&lt; (int)pVtab[3] &lt;&lt; endl;</p>
<p>&nbsp;</p>
<p>其运行结果如下所示：（在VC++ 2003和G++ 3.4.4下）</p>
<p>&nbsp;</p>
<p>[0] GrandChild::_vptr-&gt;</p>
<p>&nbsp;&nbsp;&nbsp; [0] GrandChild::f()</p>
<p>&nbsp;&nbsp;&nbsp; [1] Parent::g()</p>
<p>&nbsp;&nbsp;&nbsp; [2] Parent::h()</p>
<p>&nbsp;&nbsp;&nbsp; [3] GrandChild::g_child()</p>
<p>&nbsp;&nbsp;&nbsp; [4] Child::h1()</p>
<p>&nbsp;&nbsp;&nbsp; [5] GrandChild::h_grandchild()</p>
<p>[1] Parent.iparent = 10</p>
<p>[2] Child.ichild = 100</p>
<p>[3] GrandChild.igrandchild = 1000<br>&nbsp;</p>
<p>&nbsp;</p>
<p>使用图片表示如下：</p>
<p><br>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>可见以下几个方面：</p>
<p>1）虚函数表在最前面的位置。</p>
<p>2）成员变量根据其继承和声明顺序依次放在后面。</p>
<p>3）在单一的继承中，被overwrite的虚函数在虚函数表中得到了更新。</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>多重继承<br>&nbsp;</p>
<p>下面，再让我们来看看多重继承中的情况，假设有下面这样一个类的继承关系。注意：子类只overwrite了父类的f()函数，而还有一个是自己的函数（我们这样做的目的是为了用g1()作为一个标记来标明子类的虚函数表）。而且每个类中都有一个自己的成员变量：</p>
<p>&nbsp;</p>
<p><br>&nbsp;</p>
<p>&nbsp;</p>
<p>我们的类继承的源代码如下所示：父类的成员初始为10，20，30，子类的为100</p>
<p>&nbsp;</p>
<p>class Base1 {</p>
<p>public:</p>
<p>&nbsp;&nbsp;&nbsp; int ibase1;</p>
<p>&nbsp;&nbsp;&nbsp; Base1():ibase1(10) {}</p>
<p>&nbsp;&nbsp;&nbsp; virtual void f() { cout &lt;&lt; "Base1::f()" &lt;&lt; endl; }</p>
<p>&nbsp;&nbsp;&nbsp; virtual void g() { cout &lt;&lt; "Base1::g()" &lt;&lt; endl; }</p>
<p>&nbsp;&nbsp;&nbsp; virtual void h() { cout &lt;&lt; "Base1::h()" &lt;&lt; endl; }</p>
<p>&nbsp;</p>
<p>};</p>
<p>&nbsp;</p>
<p>class Base2 {</p>
<p>public:</p>
<p>&nbsp;&nbsp;&nbsp; int ibase2;</p>
<p>&nbsp;&nbsp;&nbsp; Base2():ibase2(20) {}</p>
<p>&nbsp;&nbsp;&nbsp; virtual void f() { cout &lt;&lt; "Base2::f()" &lt;&lt; endl; }</p>
<p>&nbsp;&nbsp;&nbsp; virtual void g() { cout &lt;&lt; "Base2::g()" &lt;&lt; endl; }</p>
<p>&nbsp;&nbsp;&nbsp; virtual void h() { cout &lt;&lt; "Base2::h()" &lt;&lt; endl; }</p>
<p>};</p>
<p>&nbsp;</p>
<p>class Base3 {</p>
<p>public:</p>
<p>&nbsp;&nbsp;&nbsp; int ibase3;</p>
<p>&nbsp;&nbsp;&nbsp; Base3():ibase3(30) {}</p>
<p>&nbsp;&nbsp;&nbsp; virtual void f() { cout &lt;&lt; "Base3::f()" &lt;&lt; endl; }</p>
<p>&nbsp;&nbsp;&nbsp; virtual void g() { cout &lt;&lt; "Base3::g()" &lt;&lt; endl; }</p>
<p>&nbsp;&nbsp;&nbsp; virtual void h() { cout &lt;&lt; "Base3::h()" &lt;&lt; endl; }</p>
<p>};</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>class Derive : public Base1, public Base2, public Base3 {</p>
<p>public:</p>
<p>&nbsp;&nbsp;&nbsp; int iderive;</p>
<p>&nbsp;&nbsp;&nbsp; Derive():iderive(100) {}</p>
<p>&nbsp;&nbsp;&nbsp; virtual void f() { cout &lt;&lt; "Derive::f()" &lt;&lt; endl; }</p>
<p>&nbsp;&nbsp;&nbsp; virtual void g1() { cout &lt;&lt; "Derive::g1()" &lt;&lt; endl; }</p>
<p>};</p>
<p>&nbsp;</p>
<p>我们通过下面的程序来查看子类实例的内存布局：下面程序中，注意我使用了一个s变量，其中用到了sizof(Base)来找下一个类的偏移量。（因为我声明的是int成员，所以是4个字节，所以没有对齐问题。关于内存的对齐问题，大家可以自行试验，我在这里就不多说了）</p>
<p>&nbsp;</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; typedef void(*Fun)(void);</p>
<p>&nbsp;</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Derive d;</p>
<p>&nbsp;</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; int** pVtab = (int**)&amp;d;</p>
<p>&nbsp;</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; cout &lt;&lt; "[0] Base1::_vptr-&gt;" &lt;&lt; endl;</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; pFun = (Fun)pVtab[0][0];</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; cout &lt;&lt; "&nbsp;&nbsp;&nbsp;&nbsp; [0] ";</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; pFun();</p>
<p>&nbsp;</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; pFun = (Fun)pVtab[0][1];</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; cout &lt;&lt; "&nbsp;&nbsp;&nbsp;&nbsp; [1] ";pFun();</p>
<p>&nbsp;</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; pFun = (Fun)pVtab[0][2];</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; cout &lt;&lt; "&nbsp;&nbsp;&nbsp;&nbsp; [2] ";pFun();</p>
<p>&nbsp;</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; pFun = (Fun)pVtab[0][3];</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; cout &lt;&lt; "&nbsp;&nbsp;&nbsp;&nbsp; [3] "; pFun();</p>
<p>&nbsp;</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; pFun = (Fun)pVtab[0][4];</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; cout &lt;&lt; "&nbsp;&nbsp;&nbsp;&nbsp; [4] "; cout&lt;&lt;pFun&lt;&lt;endl;</p>
<p>&nbsp;</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; cout &lt;&lt; "[1] Base1.ibase1 = " &lt;&lt; (int)pVtab[1] &lt;&lt; endl;</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; int s = sizeof(Base1)/4;</p>
<p>&nbsp;</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; cout &lt;&lt; "[" &lt;&lt; s &lt;&lt; "] Base2::_vptr-&gt;"&lt;&lt;endl;</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; pFun = (Fun)pVtab[s][0];</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; cout &lt;&lt; "&nbsp;&nbsp;&nbsp;&nbsp; [0] "; pFun();</p>
<p>&nbsp;</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Fun = (Fun)pVtab[s][1];</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; cout &lt;&lt; "&nbsp;&nbsp;&nbsp;&nbsp; [1] "; pFun();</p>
<p>&nbsp;</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; pFun = (Fun)pVtab[s][2];</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; cout &lt;&lt; "&nbsp;&nbsp;&nbsp;&nbsp; [2] "; pFun(); </p>
<p>&nbsp;</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; pFun = (Fun)pVtab[s][3];</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; out &lt;&lt; "&nbsp;&nbsp;&nbsp;&nbsp; [3] ";</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; cout&lt;&lt;pFun&lt;&lt;endl;</p>
<p>&nbsp;</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; cout &lt;&lt; "["&lt;&lt; s+1 &lt;&lt;"] Base2.ibase2 = " &lt;&lt; (int)pVtab[s+1] &lt;&lt; endl;</p>
<p>&nbsp;</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; s = s + sizeof(Base2)/4;</p>
<p>&nbsp;</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; cout &lt;&lt; "[" &lt;&lt; s &lt;&lt; "] Base3::_vptr-&gt;"&lt;&lt;endl;</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; pFun = (Fun)pVtab[s][0];</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; cout &lt;&lt; "&nbsp;&nbsp;&nbsp;&nbsp; [0] "; pFun();</p>
<p>&nbsp;</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; pFun = (Fun)pVtab[s][1];</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; cout &lt;&lt; "&nbsp;&nbsp;&nbsp;&nbsp; [1] "; pFun();</p>
<p>&nbsp;</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; pFun = (Fun)pVtab[s][2];</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; cout &lt;&lt; "&nbsp;&nbsp;&nbsp;&nbsp; [2] "; pFun(); </p>
<p>&nbsp;</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; pFun = (Fun)pVtab[s][3];</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; cout &lt;&lt; "&nbsp;&nbsp;&nbsp;&nbsp; [3] ";</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; cout&lt;&lt;pFun&lt;&lt;endl;</p>
<p>&nbsp;</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; s++;</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; cout &lt;&lt; "["&lt;&lt; s &lt;&lt;"] Base3.ibase3 = " &lt;&lt; (int)pVtab[s] &lt;&lt; endl;</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; s++;</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; cout &lt;&lt; "["&lt;&lt; s &lt;&lt;"] Derive.iderive = " &lt;&lt; (int)pVtab[s] &lt;&lt; endl;</p>
<p>&nbsp;</p>
<p>其运行结果如下所示：（在VC++ 2003和G++ 3.4.4下）</p>
<p>&nbsp;</p>
<p>[0] Base1::_vptr-&gt;</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp; [0] Derive::f()</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp; [1] Base1::g()</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp; [2] Base1::h()</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp; [3] Driver::g1()</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp; [4] 00000000&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &#231; 注意：在GCC下，这里是1</p>
<p>[1] Base1.ibase1 = 10</p>
<p>[2] Base2::_vptr-&gt;</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp; [0] Derive::f()</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp; [1] Base2::g()</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp; [2] Base2::h()</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp; [3] 00000000&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &#231; 注意：在GCC下，这里是1</p>
<p>[3] Base2.ibase2 = 20</p>
<p>[4] Base3::_vptr-&gt;</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp; [0] Derive::f()</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp; [1] Base3::g()</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp; [2] Base3::h()</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp; [3] 00000000</p>
<p>[5] Base3.ibase3 = 30</p>
<p>[6] Derive.iderive = 100<br>&nbsp;</p>
<p><br>使用图片表示是下面这个样子：</p>
<p>&nbsp;</p>
<p><br>&nbsp;</p>
<p>我们可以看到：</p>
<p>1）&nbsp; 每个父类都有自己的虚表。</p>
<p>2）&nbsp; 子类的成员函数被放到了第一个父类的表中。</p>
<p>3）&nbsp; 内存布局中，其父类布局依次按声明顺序排列。</p>
<p>4）&nbsp; 每个父类的虚表中的f()函数都被overwrite成了子类的f()。这样做就是为了解决不同的父类类型的指针指向同一个子类实例，而能够调用到实际的函数。</p>
<p>&nbsp;</p>
<p>本文来自CSDN博客，转载请标明出处：<a href="http://blog.csdn.net/haoel/archive/2008/10/15/3081328.aspx">http://blog.csdn.net/haoel/archive/2008/10/15/3081328.aspx</a></p>
<img src ="http://www.cppblog.com/SpringSnow/aggbug/89177.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/SpringSnow/" target="_blank">Sandy</a> 2009-07-03 17:20 <a href="http://www.cppblog.com/SpringSnow/articles/89177.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>转:C++ 代码技巧_头文件依赖，Pimpl法</title><link>http://www.cppblog.com/SpringSnow/articles/80111.html</link><dc:creator>Sandy</dc:creator><author>Sandy</author><pubDate>Thu, 16 Apr 2009 02:11:00 GMT</pubDate><guid>http://www.cppblog.com/SpringSnow/articles/80111.html</guid><wfw:comment>http://www.cppblog.com/SpringSnow/comments/80111.html</wfw:comment><comments>http://www.cppblog.com/SpringSnow/articles/80111.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/SpringSnow/comments/commentRss/80111.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/SpringSnow/services/trackbacks/80111.html</trackback:ping><description><![CDATA[今天在首页看到的,感觉不错,摘过来保存一下:<a href="http://www.cppblog.com/CornerZhang/archive/2009/04/13/79004.html"><br>http://www.cppblog.com/CornerZhang/archive/2009/04/13/79004.html</a><br>内容如下:<br><br>&nbsp;头文件依赖，Pimpl法，加速编译<br>&nbsp;&nbsp;&nbsp;举个例子:<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;// File: SoundSystem.h<br><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;#include "StreamFilter.h"<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;#include "Emitters."<br><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a title=class href="http://www.cppblog.com/CornerZhang"><font color=#8d8c8c>class</font></a> SoundSystem {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;public:<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;// ...<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;private:<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;StreamFilter&nbsp;currentFilter;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;EmitModeConfig modeConfig;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;};<br>&nbsp;&nbsp;&nbsp;一目了然的是，看得出SoundSystem实现使用了StreamFilter和EmitModeConfig的定义，所以#include 了他们的定义在此SoundSystem.h中，可是随着项目的不断推进，class SoundSystem中依赖的使用类型会增多，它的header被引入到其它模块中，不知不觉的编译时间越来越长，改进之：<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;// File: SoundSystem.h<br><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a title=class href="http://www.cppblog.com/CornerZhang"><font color=#8d8c8c>class</font></a> StreamFilter;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a title=class href="http://www.cppblog.com/CornerZhang"><font color=#8d8c8c>class</font></a> EmitModeConfig;<br><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a title=class href="http://www.cppblog.com/CornerZhang"><font color=#8d8c8c>class</font></a> SoundSystem {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;public:<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;// ...<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;private:<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;StreamFilter*&nbsp;currentFilterPtr;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;EmitModeConfig* modeConfigPtr;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;};<br><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;// File: SoundSystem.cpp<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;#include "StreamFilter.h"<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;#include "Emitters."<br><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;SoundSystem::SoundSystem() {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;//...<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;currentFilterPtr = new StreamFilter;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;modeConfigPtr = new EmitModeConfig;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;SoundSystem::~SoundSystem() {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;delete currentFilterPtr;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;delete modeConfigPtr;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;//...<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;这么一来，把StreamFilter和EmitModeConfig的#include藏到了SoundSystem的实现代码中，以后对SoundSystem的部分改动不会导致其它模块的rebuild哦，不过由此可能会牺牲一点效率吧!
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;记得，有位微软的C++翘楚人物，Herb Sutter给这种技巧称为Pimpl ( Private Implemention ), 用的恰到好处时，可以提高项目开发速度，同时模块的头文件间的#include关系得以缓解，可以避开循环依赖，而且可以获得一个良好的物理设计。<br><br>总结:<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Pimpl方法感觉很不错,<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 使用这个方法的时候,一定要注意的是在这个地方的变化,这个是我第二遍看的时候才注意到的.<br>&nbsp;&nbsp;&nbsp;&nbsp; <a title=class href="http://www.cppblog.com/CornerZhang"><font color=#8d8c8c>class</font></a> SoundSystem {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;public:<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;// ...<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;private:<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="COLOR: red">StreamFilter&nbsp;currentFilter;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;EmitModeConfig modeConfig;</span><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;};<br><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 采用Pimpl方法后,变为<br>&nbsp;&nbsp;&nbsp;&nbsp; <a title=class href="http://www.cppblog.com/CornerZhang"><font color=#8d8c8c>class</font></a> SoundSystem {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;public:<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;// ...<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;private:<br><span style="COLOR: red">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;StreamFilter*&nbsp;currentFilterPtr;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;EmitModeConfig* modeConfigPtr;</span><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;};<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 所以在.cpp文件中就有了new和delete的操作.<br><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 对于这种方法有一个疑问?对于那种存在包含众多类的情况下,这种方法的驾驭不是一般人能够掌握的吧.或许这种方法就不太使用了,不如等待一会,编译.<br><br><br></p>
<img src ="http://www.cppblog.com/SpringSnow/aggbug/80111.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/SpringSnow/" target="_blank">Sandy</a> 2009-04-16 10:11 <a href="http://www.cppblog.com/SpringSnow/articles/80111.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>转:进程的互斥运行</title><link>http://www.cppblog.com/SpringSnow/articles/70466.html</link><dc:creator>Sandy</dc:creator><author>Sandy</author><pubDate>Fri, 26 Dec 2008 10:46:00 GMT</pubDate><guid>http://www.cppblog.com/SpringSnow/articles/70466.html</guid><wfw:comment>http://www.cppblog.com/SpringSnow/comments/70466.html</wfw:comment><comments>http://www.cppblog.com/SpringSnow/articles/70466.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/SpringSnow/comments/commentRss/70466.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/SpringSnow/services/trackbacks/70466.html</trackback:ping><description><![CDATA[<strong>进程的互斥运行<br><a href="http://bbs.onlycpp.net/viewthread.php?tid=540&amp;extra=page%3D1">http://bbs.onlycpp.net/viewthread.php?tid=540&amp;extra=page%3D1</a><br><br></strong>　　正常情况下，一个进程的运行一般是不会影响到其他正在运行的进程的。但是对于某些有特殊要求的如以独占方式使用串行口等硬件设备的<span class=t_tag onclick=tagshow(event) href="tag.php?name=%B3%CC%D0%F2">程序</span>就要求在其进程运行期间不允许其他试图使用此端口设备的程序运行的，而且此类程序通常也不允许运行同一个程序的多个实例。这就引出了进程互斥的问题。<br><br>　　实现进程互斥的核心思想比较简单：进程在启动时首先检查当前系统是否已经存在有此进程的实例，如果没有，进程将成功创建并设置标识实例已经存在的标记。此后再创建进程时将会通过该标记而知晓其实例已经存在，从而保证进程在系统中只能存在一个实例。具体可以采取<span class=t_tag onclick=tagshow(event) href="tag.php?name=%C4%DA%B4%E6">内存</span>映射<span class=t_tag onclick=tagshow(event) href="tag.php?name=%CE%C4%BC%FE">文件</span>、有名事件量、有名互斥量以及全局共享变量等多种方法来实现。下面就分别对其中具有代表性的有名互斥量和全局共享变量这两种方法进行介绍：<br>
<table class=t_table style="WIDTH: 95%" cellSpacing=0 bgColor=#333333>
    <tbody>
        <tr bgColor=#ffffff>
            <td><font face="Fixedsys ">// 创建互斥量<br>HANDLE m_hMutex = CreateMutex(NULL, FALSE, "Sample07");<br>// 检查错误代码<br>if (GetLastError() == ERROR_ALREADY_EXISTS) {<br>　// 如果已有互斥量存在则释放<span class=t_tag onclick=tagshow(event) href="tag.php?name=%BE%E4%B1%FA">句柄</span>并复位互斥量<br>　CloseHandle(m_hMutex);<br>　m_hMutex = NULL;<br>　// 程序退出<br>　return FALSE;<br>}</font></td>
        </tr>
    </tbody>
</table>
<br>　　上面这段代码演示了有名互斥量在进程互斥中的<span class=t_tag onclick=tagshow(event) href="tag.php?name=%D3%C3%B7%A8">用法</span>。代码的核心是CreateMutex（）对有名互斥量的创建。CreateMutex（）<span class=t_tag onclick=tagshow(event) href="tag.php?name=%BA%AF%CA%FD">函数</span>可用来创建一个有名或无名的互斥量对象，其函数原型为：<br>
<table class=t_table style="WIDTH: 95%" cellSpacing=0 bgColor=#333333>
    <tbody>
        <tr bgColor=#ffffff>
            <td><font face="Fixedsys ">HANDLE CreateMutex(<br>　LPSECURITY_ATTRIBUTES lpMutexAttributes, // 指向安全属性的<span class=t_tag onclick=tagshow(event) href="tag.php?name=%D6%B8%D5%EB">指针</span><br>　BOOL bInitialOwner, // 初始化互斥对象的所有者<br>　LPCTSTR lpName // 指向互斥对象名的指针<br>);</font></td>
        </tr>
    </tbody>
</table>
<br>　　如果函数成功执行，将返回一个互斥量对象的句柄。如果在CreateMutex（）执行前已经存在有相同名字的互斥量，函数将返回这个已经存在互斥量的句柄，并且可以通过GetLastError（）得到错误代码ERROR_ALREADY_EXIST。可见，通过对错误代码ERROR_ALREADY_EXIST的检测可以实现CreateMutex（）对进程的互斥。<br>&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;建立互斥体，用来同步。如果一个线程<span class=t_tag onclick=tagshow(event) href="tag.php?name=%BB%F1%C8%A1">获取</span>了互斥体，则要获取该互斥体的第二个线程将被挂起，直到第一个线程释放该互斥体。 <br><br>参数 <br>lpMutexAttributes <br>指向一个SECURITY_ATTRIBUTES结构的指针，这个结构决定互斥体句柄是否被子进程<span class=t_tag onclick=tagshow(event) href="tag.php?name=%BC%CC%B3%D0">继承</span>。&nbsp; &nbsp;&nbsp;&nbsp;<br>bInitialOwner <br>布尔<span class=t_tag onclick=tagshow(event) href="tag.php?name=%C0%E0%D0%CD">类型</span>，决定互斥体的创建者是否为拥有者 <br>lpName <br>指向互斥体名字<span class=t_tag onclick=tagshow(event) href="tag.php?name=%D7%D6%B7%FB">字符</span>串的指针。互斥体可以有名字。 <br>互斥体的好处是可以在进程间共享<br><strong>心得体会：<br></strong><font face="Fixedsys ">&nbsp; &nbsp; CreateMutex() </font><font face="Arial ">用于有独占要求的程序 (在其进程运行期间不允许其他使用此端口设备的程序运行，或不允许同名程序运行)。如有同名程序运行，则通过 <strong>GetLastError（）</strong>得到错误代码 <strong>ERROR_ALREADY_EXIST</strong>。<br><br>刚才又执行了下得出的结果（程序名samp）<br>&nbsp; &nbsp;&nbsp; &nbsp; 一般情况下：一进入调试阶段，进程管理器中就出现了samp进程，执行到<font face="Fixedsys "><font face="Arial ">CreateMutex时返回进程句柄，执行到if(GetLastError() == ERROR_ALREADY_EXISTS ) 进行判断时，跳过不执行if中的内容，所以表示没有互斥。<br>&nbsp; &nbsp;&nbsp; &nbsp; 调试之前先运行debug中的samp.exe再调试：一进入调试阶段，进程管理器中就出现了两个samp进程，执行到CreateMutex时返回进程句柄，执行到if(GetLastError() == ERROR_ALREADY_EXISTS ) 进行判断时，执行if中的内容，表示有互斥。</font></font></font>
<img src ="http://www.cppblog.com/SpringSnow/aggbug/70466.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/SpringSnow/" target="_blank">Sandy</a> 2008-12-26 18:46 <a href="http://www.cppblog.com/SpringSnow/articles/70466.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>转: c++ notes</title><link>http://www.cppblog.com/SpringSnow/articles/70378.html</link><dc:creator>Sandy</dc:creator><author>Sandy</author><pubDate>Fri, 26 Dec 2008 01:15:00 GMT</pubDate><guid>http://www.cppblog.com/SpringSnow/articles/70378.html</guid><wfw:comment>http://www.cppblog.com/SpringSnow/comments/70378.html</wfw:comment><comments>http://www.cppblog.com/SpringSnow/articles/70378.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/SpringSnow/comments/commentRss/70378.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/SpringSnow/services/trackbacks/70378.html</trackback:ping><description><![CDATA[<p><a href="http://www.cppblog.com/mymsdn/archive/2008/12/25/cpp-notes-3.html">http://www.cppblog.com/mymsdn/archive/2008/12/25/cpp-notes-3.html</a><br>21、vector的动态增长优于预先分配内存。</p>
<p>使用vector的时候最好动态地添加元素。它不同于C和Java或其他语言的数据类型，为了达到连续性，更有效的方法是先初始化一个空vector对象，然后再动态添加元素，而不是预先分配内存。</p>
<p>22、vector值初始化</p>
<p>内置-&gt;0</p>
<p>有默认构造-&gt;调用默认构造</p>
<p>无默认构造，有其他构造-&gt;程序员手动提供初始值</p>
<p>无默认构造，也无其他构造-&gt;标准库产生一个带初值的对象</p>
<p>23、数组下标的类型</p>
<p>C++中，数组下标的正确类型是size_t而不是int，size_t是一个与机器相关的unsigned类型。</p>
<p>24、在声明指针的时候，可以用空格将符号*与其后的标识符分隔开来，string *ps与string* ps都是可以的，但后者容易产生误解，如：</p>
<p>string* ps1,ps2;&nbsp;&nbsp;&nbsp;&nbsp; //ps1是指针，而ps2是一个string对象</p>
<p>也就是说，人们可能误把string和string*当作两个类型，或者说string*被当作一种新类型来看待，<u>但这是错的</u>！</p>
<p>25、一个有效的指针必然是以下三种状态之一：</p>
<ol>
    <li>保存特定的对象的地址；
    <li>指向某个对象后面的另一对象；
    <li>或者是0值。表明它不指向任何对象。</li>
</ol>
<p>其中int *pi=0;与int *pi;是不同的。前者是初始化指针指向0地址的对象（即为NULL）（pi initialized to address to no object），后者却是未初始化的（ok, but dangerous, pi is uninitialized）。</p>
<p>编译器可以检测出0值的指针，程序可判断该指针并未指向一个对象，而未初始化的指针的使用标准并未定义，对大多数编译器来说，如果使用未初始化的指针会将指针中存放的不确定值视为地址，然后操纵该内存地址中存放的位内容，使用未初始化的指针相当于操纵这个不确定的地址中存储的基础数据，因此对未初始化的指针进行解引用时，通常会导致程序崩溃。</p>
<p>26、void*指针</p>
<p>void*指针只支持几种有限的操作：</p>
<ol>
    <li>与另一个指针进行比较；
    <li>向函数传递void*指针或从函数返回void*指针；
    <li>给另一个void*指针赋值。</li>
</ol>
<p>不允许使用void*指针操纵它所指向的对象。</p>
<p>27、指针和引用的比较（P105）</p>
<p class=quote>虽然使用引用（reference）和指针都可间接访问另一个值，但它们之间有两个重要区别。第一个区别在于引用总是指向某个对象：定义引用时没有初始化是错误的。第二个重要区别则是赋值行为的差异：给引用赋值修改的是该引用所关联的对象的值，而并不是使引用与另一个对象关联。引用一经初始化，就始终指向同一个特定对象（这就是为什么引用必须在定义时初始化的原因）。</p>
<p>28、指针与typedef（P112）</p>
<p>const放在类型前和放在类型后都可以表示同样的意思：</p>
<pre class=gc-code><span style="COLOR: #0000ff">const</span> <span style="COLOR: #0000ff">string</span> s1;
<span style="COLOR: #0000ff">string</span> <span style="COLOR: #0000ff">const</span> s2;</pre>
<p>s1和s2均表示常量字符串对象。</p>
<p>但因此就导致了下面的句子可能产生误解：</p>
<pre class=gc-code><span style="COLOR: #0000ff">typedef</span> <span style="COLOR: #0000ff">string</span> *pstring;
<span style="COLOR: #0000ff">const</span> pstring cstr;</pre>
<p>容易错把typedef当成文本扩展而产生下面的理解：</p>
<pre class=gc-code><span style="COLOR: #0000ff">const</span> <span style="COLOR: #0000ff">string</span> *cstr; <span style="COLOR: #008000">//这并非上面例子的正确意思！（错误）</span></pre>
<p>应该从声明的句子看，也就是说只看const pstring cstr;，在这里pstring是一种指针类型，const修饰的是这个类型，因此正确的理解应该是：</p>
<pre class=gc-code><span style="COLOR: #0000ff">string</span> *<span style="COLOR: #0000ff">const</span> cstr;</pre>
<p>而const pstring cstr;其实可以表示为pstring const cstr;，这样的写法则不容易产生误解。从右向左阅读的意思就是：cstr是const pstring类型，即指向string对象的const指针。</p>
<p>29、创建动态数组（注意点见代码注释）</p>
<pre class=gc-code><span style="COLOR: blue">const char </span>*cp1 = <span style="COLOR: #a31515">"some value"</span>;
<span style="COLOR: blue">char </span>*cp2 = <span style="COLOR: #a31515">"other value"</span>;
<span style="COLOR: blue">int </span>*piArray1 = <span style="COLOR: blue">new int</span>[10];    <span style="COLOR: green">//内置类型没有初始化
</span><span style="COLOR: blue">int </span>*piArray2 = <span style="COLOR: blue">new int</span>[10]();    <span style="COLOR: green">//内置类型需要加空圆括号，对数组元素进行初始化
</span>std::string *psArray1 = <span style="COLOR: blue">new </span>std::string[10];    <span style="COLOR: green">//默认构造函数初始化
</span>std::cout&lt;&lt;<span style="COLOR: #a31515">"----------"</span>&lt;&lt;std::endl
&lt;&lt;<span style="COLOR: #a31515">"*cp1\t\t:"</span>&lt;&lt;*cp1&lt;&lt;std::endl
&lt;&lt;<span style="COLOR: #a31515">"*cp2\t\t:"</span>&lt;&lt;*cp2&lt;&lt;std::endl
&lt;&lt;<span style="COLOR: #a31515">"*piArray1\t:"</span>&lt;&lt;*piArray1&lt;&lt;std::endl
&lt;&lt;<span style="COLOR: #a31515">"*piArray2\t:"</span>&lt;&lt;*piArray2&lt;&lt;std::endl
&lt;&lt;<span style="COLOR: #a31515">"*psArray1\t:"</span>&lt;&lt;*psArray1&lt;&lt;std::endl
&lt;&lt;<span style="COLOR: #a31515">"----------"</span>&lt;&lt;std::endl;</pre>
<p>但是下面的结果却与概念上的不同：</p>
<pre class=gc-code><span style="COLOR: green">////Visual Studio &amp; MS VC++
//----------
//*cp1            :s
//*cp2            :o
//*piArray1       :-842150451
//*piArray2       :0
//*psArray1       :
//----------
////Eclipse&amp;G++
//----------
//*cp1        :s
//*cp2        :o
//*piArray1    :4064608
//*piArray2    :4064560
//*psArray1    :
//----------</span></pre>
<p>看来不同的编译器对此的定义还是有所不同，注意看*piArray2的值，按照说明应该是初始化为0，但这里却仍然表现出与*piArray1一样的值，说明并没有发生初始化。</p>
<p class=note>对于动态分配的数组，其元素只能初始化为元素类型的默认值，而不能像数组变量一样，用初始化列表为数组元素提供各不相同的初值。</p>
<p>30、const对象的动态数组</p>
<pre class=gc-code><span style="COLOR: green">//P118
//error:uninitialized const array
</span><span style="COLOR: blue">const int </span>*pciArray1 = <span style="COLOR: blue">new const int</span>[10];
<span style="COLOR: green">//ok:value-initialized const array
</span><span style="COLOR: blue">const int </span>*pciArray2 = <span style="COLOR: blue">new const int</span>[10]();
std::cout&lt;&lt;*pciArray1&lt;&lt;std::endl;
std::cout&lt;&lt;*pciArray2&lt;&lt;std::endl;</pre>
<p>上面的示例的注释来自书中，但在VC++编译器和G++编译器下却不同，具体表现为：</p>
<ul>
    <li>VC++：编译正确，第一句输出随机地址的值，第二句输出初始化的0（其中按照&#8220;标准&#8221;第一种因为未向const变量初始化，应该无法通过编译，但这里可以）
    <li>G++：编译错误，第一句的错误信息为&#8220;uninitialized const in `new' of `const int'&#8221;，但第二句按照标准应该输出0的，这里却输出了随机地址的值。</li>
</ul>
<p>看来两个编译器对这一问题的看法不太一致。</p>
<img src ="http://www.cppblog.com/SpringSnow/aggbug/70378.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/SpringSnow/" target="_blank">Sandy</a> 2008-12-26 09:15 <a href="http://www.cppblog.com/SpringSnow/articles/70378.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>转:使用__FILE__和__LINE__定位错误 </title><link>http://www.cppblog.com/SpringSnow/articles/70318.html</link><dc:creator>Sandy</dc:creator><author>Sandy</author><pubDate>Thu, 25 Dec 2008 05:04:00 GMT</pubDate><guid>http://www.cppblog.com/SpringSnow/articles/70318.html</guid><wfw:comment>http://www.cppblog.com/SpringSnow/comments/70318.html</wfw:comment><comments>http://www.cppblog.com/SpringSnow/articles/70318.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/SpringSnow/comments/commentRss/70318.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/SpringSnow/services/trackbacks/70318.html</trackback:ping><description><![CDATA[<div class=postTitle><a class=postTitle2 id=viewpost1_TitleUrl href="http://www.cppblog.com/heath/archive/2008/08/05/58046.html"><u><font color=#0000ff>使用__FILE__和__LINE__定位错误</font></u></a> <br>转自:http://www.cppblog.com/heath/archive/2008/12/25/58046.html#70307</div>
[前言：使用__FILE__和__LINE__来定位错误已经屡见不鲜，然而其中一些道理又有几个人仔细探究过。本文参考了Curtis Krauskopf的一篇名为<a href="http://www.decompile.com/cpp/faq/file_and_line_error_string.htm" target=_blank><u><font color=#0000ff>Using __FILE__ and __LINE__ to Report Errors</font></u></a>的文章，希望达到解惑之效。]<br><br>
<p class=FAQQ>问题：当运行时错误产生时，我怎样才能得到包含C++文件名和行号的字符串信息？<br><span class=FAQA>回答：在C++中的__FILE__预编译指示器包含了被编译的文件名，而__LINE__则包含了源代码的行号。__FILE__和__LINE__的前后都包含了<span style="COLOR: red">两个<span style="COLOR: #000000">下划线，让我们仔细看看__FILE__所包含的每个字符：</span></span>
<p>_ _ F I L E _ _ <br><br></span>下面展示了在控制台程序中如果显示文件名和代码行号。<br><br></p>
<div style="BORDER-RIGHT: #cccccc 1px solid; PADDING-RIGHT: 5px; BORDER-TOP: #cccccc 1px solid; PADDING-LEFT: 4px; FONT-SIZE: 10pt; PADDING-BOTTOM: 4px; BORDER-LEFT: #cccccc 1px solid; WIDTH: 98%; WORD-BREAK: break-all; PADDING-TOP: 4px; BORDER-BOTTOM: #cccccc 1px solid; BACKGROUND-COLOR: #eeeeee">
<p align=left><span style="COLOR: #000000">#include&nbsp;</span><span style="COLOR: #000000">&lt;</span><span style="COLOR: #000000">stdio.h</span><span style="COLOR: #000000">&gt;</span><span style="COLOR: #000000"><br><br></span><span style="COLOR: #0000ff">int</span><span style="COLOR: #000000">&nbsp;main(</span><span style="COLOR: #0000ff">int</span><span style="COLOR: #000000">&nbsp;,&nbsp;</span><span style="COLOR: #0000ff">char</span><span style="COLOR: #000000">**</span><span style="COLOR: #000000">)<br>{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;printf(</span><span style="COLOR: #000000">"</span><span style="COLOR: #000000">This&nbsp;fake&nbsp;error&nbsp;is&nbsp;in&nbsp;%s&nbsp;on&nbsp;line&nbsp;%d\n</span><span style="COLOR: #000000">"</span><span style="COLOR: #000000">,&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;__FILE__,&nbsp;__LINE__);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="COLOR: #0000ff">return</span><span style="COLOR: #000000">&nbsp;</span><span style="COLOR: #000000">0</span><span style="COLOR: #000000">;<br>}</span></p>
</div>
<p><br>输出结果：<br><br><span>This fake error is in c:\temp\test.cpp on line 5<br><br></span></p>
<span style="FONT-FAMILY: 黑体">让我们更上一层楼</span> <br>
<p>我想通过一个通用函数error()来报告错误，以使当某个错误发生时我能设置断点以及隔离错误处理（例如，在屏幕上打印错误信息或者写入日志）。因此，函数的原型应该是这样的吧：<br><br></p>
<div style="BORDER-RIGHT: #cccccc 1px solid; PADDING-RIGHT: 5px; BORDER-TOP: #cccccc 1px solid; PADDING-LEFT: 4px; FONT-SIZE: 10pt; PADDING-BOTTOM: 4px; BORDER-LEFT: #cccccc 1px solid; WIDTH: 98%; WORD-BREAK: break-all; PADDING-TOP: 4px; BORDER-BOTTOM: #cccccc 1px solid; BACKGROUND-COLOR: #eeeeee"><span style="COLOR: #0000ff">void</span><span style="COLOR: #000000">&nbsp;error(</span><span style="COLOR: #0000ff">const</span><span style="COLOR: #000000">&nbsp;</span><span style="COLOR: #0000ff">char</span><span style="COLOR: #000000">&nbsp;</span><span style="COLOR: #000000">*</span><span style="COLOR: #000000">file,&nbsp;</span><span style="COLOR: #0000ff">const</span><span style="COLOR: #000000">&nbsp;unsigned&nbsp;</span><span style="COLOR: #0000ff">long</span><span style="COLOR: #000000">&nbsp;line, </span><span style="COLOR: #0000ff">const</span><span style="COLOR: #000000">&nbsp;</span><span style="COLOR: #0000ff">char</span><span style="COLOR: #000000">&nbsp;</span><span style="COLOR: #000000">*</span><span style="COLOR: #000000">msg);&nbsp;</span></div>
<br>调用方式如下：<br><br>
<div style="BORDER-RIGHT: #cccccc 1px solid; PADDING-RIGHT: 5px; BORDER-TOP: #cccccc 1px solid; PADDING-LEFT: 4px; FONT-SIZE: 10pt; PADDING-BOTTOM: 4px; BORDER-LEFT: #cccccc 1px solid; WIDTH: 98%; WORD-BREAK: break-all; PADDING-TOP: 4px; BORDER-BOTTOM: #cccccc 1px solid; BACKGROUND-COLOR: #eeeeee"><span style="COLOR: #000000">error(__FILE__,&nbsp;__LINE__,&nbsp;</span><span style="COLOR: #000000">"</span><span style="COLOR: #000000">my&nbsp;error&nbsp;message</span><span style="COLOR: #000000">"</span><span style="COLOR: #000000">);</span></div>
<br><span style="FONT-FAMILY: 黑体">预处理魔法<br>
<p><span style="FONT-FAMILY: 宋体">这里有三个问题需要解决：</span></p>
<ol>
    <li><span style="FONT-FAMILY: 宋体">__FILE__和__LINE__在每次调用error时作为参数传入。</span>
    <li><span style="FONT-FAMILY: 宋体">__FILE和__LINE__前后的下划线很容易被遗忘，从而导致编译错误。</span>
    <li>__LINE__<span style="FONT-FAMILY: 宋体">是一个整数，这无疑增加了error函数的复杂度。我绝不想直接使用整型的__LINE__，而通常都是将转换为字符串打印到屏幕或写入日志文件。<br></span></li>
</ol>
<p><span style="FONT-FAMILY: 宋体">__FILE__和__LINE__应该被自动处理，而非每次作为参数传递给error，这样会让error的使用者感觉更爽些，它的形式可能是下面这样：<br><br></p>
<div style="BORDER-RIGHT: #cccccc 1px solid; PADDING-RIGHT: 5px; BORDER-TOP: #cccccc 1px solid; PADDING-LEFT: 4px; FONT-SIZE: 10pt; PADDING-BOTTOM: 4px; BORDER-LEFT: #cccccc 1px solid; WIDTH: 98%; WORD-BREAK: break-all; PADDING-TOP: 4px; BORDER-BOTTOM: #cccccc 1px solid; BACKGROUND-COLOR: #eeeeee"><span style="COLOR: #000000">error(AT,&nbsp;</span><span style="COLOR: #000000">"</span><span style="COLOR: #000000">my&nbsp;error&nbsp;message</span><span style="COLOR: #000000">"</span><span style="COLOR: #000000">);</span></div>
<p>&nbsp;<br>我希望上面的宏AT展开为：<span class=cdQ>"c:\temp\test.cpp:5"。而新的error函数则变成：<br><br></p>
<div style="BORDER-RIGHT: #cccccc 1px solid; PADDING-RIGHT: 5px; BORDER-TOP: #cccccc 1px solid; PADDING-LEFT: 4px; FONT-SIZE: 10pt; PADDING-BOTTOM: 4px; BORDER-LEFT: #cccccc 1px solid; WIDTH: 98%; WORD-BREAK: break-all; PADDING-TOP: 4px; BORDER-BOTTOM: #cccccc 1px solid; BACKGROUND-COLOR: #eeeeee"><span style="COLOR: #0000ff">void</span><span style="COLOR: #000000">&nbsp;error(</span><span style="COLOR: #0000ff">const</span><span style="COLOR: #000000">&nbsp;</span><span style="COLOR: #0000ff">char</span><span style="COLOR: #000000">&nbsp;</span><span style="COLOR: #000000">*</span><span style="COLOR: #000000">location,&nbsp;</span><span style="COLOR: #0000ff">const</span><span style="COLOR: #000000">&nbsp;</span><span style="COLOR: #0000ff">char</span><span style="COLOR: #000000">&nbsp;</span><span style="COLOR: #000000">*</span><span style="COLOR: #000000">msg);</span></div>
<p></span>&nbsp;</p>
<p>因为Borland C++ Builder编译器能够自动合并相邻的字符串，因此我把AT写成下面这样：<br><br></p>
<div style="BORDER-RIGHT: #cccccc 1px solid; PADDING-RIGHT: 5px; BORDER-TOP: #cccccc 1px solid; PADDING-LEFT: 4px; FONT-SIZE: 10pt; PADDING-BOTTOM: 4px; BORDER-LEFT: #cccccc 1px solid; WIDTH: 98%; WORD-BREAK: break-all; PADDING-TOP: 4px; BORDER-BOTTOM: #cccccc 1px solid; BACKGROUND-COLOR: #eeeeee"><span style="COLOR: #0000ff">#define</span><span style="COLOR: #000000">&nbsp;AT&nbsp;__FILE__&nbsp;":"&nbsp;__LINE__&nbsp;</span></div>
<p><br>然而它却罢工了，因为__LINE__被扩展成了整数而非字符串，所以宏展开后变成：<br><span class=cdQ><br>"c:\temp\test.cpp" ":"</span> <span class=cdC>5</span>
<p>这是一个无效的字符串，因为末尾有一个未被双引号包含的整数。
<p>怎么办？别着急，一个特殊的预编译指示器&#8220;#&#8221;能够帮我们将一个变量转换成字符串。所以重新定义宏：
<pre class=cdCode>
<div style="BORDER-RIGHT: #cccccc 1px solid; PADDING-RIGHT: 5px; BORDER-TOP: #cccccc 1px solid; PADDING-LEFT: 4px; FONT-SIZE: 10pt; PADDING-BOTTOM: 4px; BORDER-LEFT: #cccccc 1px solid; WIDTH: 98%; WORD-BREAK: break-all; PADDING-TOP: 4px; BORDER-BOTTOM: #cccccc 1px solid; BACKGROUND-COLOR: #eeeeee"><span style="COLOR: #0000ff">#define</span><span style="COLOR: #000000">&nbsp;AT&nbsp;__FILE__&nbsp;":"&nbsp;#__LINE__</span></div>
</pre>
<pre class=cdCode><span style="FONT-SIZE: 12pt">嘿嘿，这样总行了吧。别高兴得太早，这样也是不行的。因为编译器会抱怨#是个无效字符。其实，问题是#预编译指示器只有这样使用才会<br>被正确识别：<br>
<div style="BORDER-RIGHT: #cccccc 1px solid; PADDING-RIGHT: 5px; BORDER-TOP: #cccccc 1px solid; PADDING-LEFT: 4px; FONT-SIZE: 10pt; PADDING-BOTTOM: 4px; BORDER-LEFT: #cccccc 1px solid; WIDTH: 98%; WORD-BREAK: break-all; PADDING-TOP: 4px; BORDER-BOTTOM: #cccccc 1px solid; BACKGROUND-COLOR: #eeeeee"><span style="COLOR: #0000ff">#define</span><span style="COLOR: #000000">&nbsp;symbol(X)&nbsp;#X&nbsp;</span></div>
</span></pre>
</span></span><br><font face="宋体, MS Song">因此，我把代码改为：<br><br></font>
<div style="BORDER-RIGHT: #cccccc 1px solid; PADDING-RIGHT: 5px; BORDER-TOP: #cccccc 1px solid; PADDING-LEFT: 4px; FONT-SIZE: 10pt; PADDING-BOTTOM: 4px; BORDER-LEFT: #cccccc 1px solid; WIDTH: 98%; WORD-BREAK: break-all; PADDING-TOP: 4px; BORDER-BOTTOM: #cccccc 1px solid; BACKGROUND-COLOR: #eeeeee"><span style="COLOR: #0000ff">#define</span><span style="COLOR: #000000">&nbsp;STRINGIFY(x)&nbsp;#x</span><span style="COLOR: #000000"><br></span><span style="COLOR: #0000ff">#define</span><span style="COLOR: #000000">&nbsp;AT&nbsp;__FILE__&nbsp;":"&nbsp;STRINGIFY(__LINE__)&nbsp;</span></div>
<br>然而，奇怪的结果产生了，__LINE__居然被作为了输出的一部分：<br><br>c:\temp\test.cpp:__LINE__: fake error<br><br>
<p>解决方法是再用一个宏来包装STRINGIFY()：<br><br></p>
<div style="BORDER-RIGHT: #cccccc 1px solid; PADDING-RIGHT: 5px; BORDER-TOP: #cccccc 1px solid; PADDING-LEFT: 4px; FONT-SIZE: 10pt; PADDING-BOTTOM: 4px; BORDER-LEFT: #cccccc 1px solid; WIDTH: 98%; WORD-BREAK: break-all; PADDING-TOP: 4px; BORDER-BOTTOM: #cccccc 1px solid; BACKGROUND-COLOR: #eeeeee"><span style="COLOR: #0000ff">#define</span><span style="COLOR: #000000">&nbsp;STRINGIFY(x)&nbsp;#x</span><span style="COLOR: #000000"><br></span><span style="COLOR: #0000ff">#define</span><span style="COLOR: #000000">&nbsp;TOSTRING(x)&nbsp;STRINGIFY(x)</span><span style="COLOR: #000000"><br></span><span style="COLOR: #0000ff">#define</span><span style="COLOR: #000000">&nbsp;AT&nbsp;__FILE__&nbsp;":"&nbsp;TOSTRING(__LINE__)</span><span style="COLOR: #000000"><br></span></div>
<br>OK，我们用下面的代码来试试：<br><br>
<div style="BORDER-RIGHT: #cccccc 1px solid; PADDING-RIGHT: 5px; BORDER-TOP: #cccccc 1px solid; PADDING-LEFT: 4px; FONT-SIZE: 10pt; PADDING-BOTTOM: 4px; BORDER-LEFT: #cccccc 1px solid; WIDTH: 98%; WORD-BREAK: break-all; PADDING-TOP: 4px; BORDER-BOTTOM: #cccccc 1px solid; BACKGROUND-COLOR: #eeeeee"><span style="COLOR: #000000">#include&nbsp;</span><span style="COLOR: #000000">&lt;</span><span style="COLOR: #000000">stdio.h</span><span style="COLOR: #000000">&gt;</span><span style="COLOR: #000000"><br></span><span style="COLOR: #0000ff">#define</span><span style="COLOR: #000000">&nbsp;STRINGIFY(x)&nbsp;#x</span><span style="COLOR: #000000"><br></span><span style="COLOR: #0000ff">#define</span><span style="COLOR: #000000">&nbsp;TOSTRING(x)&nbsp;STRINGIFY(x)</span><span style="COLOR: #000000"><br></span><span style="COLOR: #0000ff">#define</span><span style="COLOR: #000000">&nbsp;AT&nbsp;__FILE__&nbsp;":"&nbsp;TOSTRING(__LINE__)</span><span style="COLOR: #000000"><br></span><span style="COLOR: #0000ff">void</span><span style="COLOR: #000000">&nbsp;error(</span><span style="COLOR: #0000ff">const</span><span style="COLOR: #000000">&nbsp;</span><span style="COLOR: #0000ff">char</span><span style="COLOR: #000000">&nbsp;</span><span style="COLOR: #000000">*</span><span style="COLOR: #000000">location,&nbsp;</span><span style="COLOR: #0000ff">const</span><span style="COLOR: #000000">&nbsp;</span><span style="COLOR: #0000ff">char</span><span style="COLOR: #000000">&nbsp;</span><span style="COLOR: #000000">*</span><span style="COLOR: #000000">msg)<br>{<br>&nbsp;&nbsp;printf(</span><span style="COLOR: #000000">"</span><span style="COLOR: #000000">Error&nbsp;at&nbsp;%s:&nbsp;%s\n</span><span style="COLOR: #000000">"</span><span style="COLOR: #000000">,&nbsp;location,&nbsp;msg);<br>}<br></span><span style="COLOR: #0000ff">int</span><span style="COLOR: #000000">&nbsp;main(</span><span style="COLOR: #0000ff">int</span><span style="COLOR: #000000">&nbsp;,&nbsp;</span><span style="COLOR: #0000ff">char</span><span style="COLOR: #000000">**</span><span style="COLOR: #000000">)<br>{<br>&nbsp;&nbsp;error(AT,&nbsp;</span><span style="COLOR: #000000">"</span><span style="COLOR: #000000">fake&nbsp;error</span><span style="COLOR: #000000">"</span><span style="COLOR: #000000">);<br>&nbsp;&nbsp;</span><span style="COLOR: #0000ff">return</span><span style="COLOR: #000000">&nbsp;</span><span style="COLOR: #000000">0</span><span style="COLOR: #000000">;<br>}<br><br></span></div>
<br>输出结果：<br><br><span style="FONT-SIZE: 12pt">Error at c:\temp\test\test.cpp:11: fake error<br><br><span style="FONT-FAMILY: 黑体">Visual Studio下的实践<br><span style="FONT-FAMILY: 宋体">在《Windows核心编程》中，Jeffrey Richter提供了下面的宏在编译期输出有用信息：</span><br><br>
<div style="BORDER-RIGHT: #cccccc 1px solid; PADDING-RIGHT: 5px; BORDER-TOP: #cccccc 1px solid; PADDING-LEFT: 4px; FONT-SIZE: 10pt; PADDING-BOTTOM: 4px; BORDER-LEFT: #cccccc 1px solid; WIDTH: 98%; WORD-BREAK: break-all; PADDING-TOP: 4px; BORDER-BOTTOM: #cccccc 1px solid; BACKGROUND-COLOR: #eeeeee"><span style="COLOR: #0000ff">#define</span><span style="COLOR: #000000">&nbsp;chSTR2(x)&nbsp;#x</span><span style="COLOR: #000000"><br></span><span style="COLOR: #0000ff">#define</span><span style="COLOR: #000000">&nbsp;chSTR(x)&nbsp;&nbsp;chSTR2(x)</span><span style="COLOR: #000000"><br></span><span style="COLOR: #0000ff">#define</span><span style="COLOR: #000000">&nbsp;chMSG(desc)&nbsp;message(__FILE__&nbsp;"("&nbsp;chSTR(__LINE__)&nbsp;"):"&nbsp;#desc)</span></div>
</span></span><br><font face="宋体, MS Song">message是一个预编译指令，上面宏的使用方法是：<br><br></font>
<div style="BORDER-RIGHT: #cccccc 1px solid; PADDING-RIGHT: 5px; BORDER-TOP: #cccccc 1px solid; PADDING-LEFT: 4px; FONT-SIZE: 10pt; PADDING-BOTTOM: 4px; BORDER-LEFT: #cccccc 1px solid; WIDTH: 98%; WORD-BREAK: break-all; PADDING-TOP: 4px; BORDER-BOTTOM: #cccccc 1px solid; BACKGROUND-COLOR: #eeeeee"><font face="宋体, MS Song"><img src="http://www.cppblog.com/Images/OutliningIndicators/None.gif" align=top></font><span style="COLOR: #000000">#pragma&nbsp;chMSG(Fix&nbsp;</span><span style="COLOR: #0000ff">this</span><span style="COLOR: #000000">&nbsp;later)</span></div>
<br><span style="FONT-FAMILY: 黑体" 12pt>结论<br>
<ol>
    <li><span style="FONT-FAMILY: 宋体" 12pt>预编译指示器__FILE__和__LINE__能够提供有用的信息。</span>
    <li><span style="FONT-FAMILY: 宋体" 12pt>__LINE__的处理方式远比我们想象的要复杂。</span>
    <li>__FILE__被处理成字符串，给我们带来了不少方便。 </li>
</ol>
</span>
<img src ="http://www.cppblog.com/SpringSnow/aggbug/70318.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/SpringSnow/" target="_blank">Sandy</a> 2008-12-25 13:04 <a href="http://www.cppblog.com/SpringSnow/articles/70318.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>转: fopen打开文件方式 </title><link>http://www.cppblog.com/SpringSnow/articles/70317.html</link><dc:creator>Sandy</dc:creator><author>Sandy</author><pubDate>Thu, 25 Dec 2008 04:59:00 GMT</pubDate><guid>http://www.cppblog.com/SpringSnow/articles/70317.html</guid><wfw:comment>http://www.cppblog.com/SpringSnow/comments/70317.html</wfw:comment><comments>http://www.cppblog.com/SpringSnow/articles/70317.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/SpringSnow/comments/commentRss/70317.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/SpringSnow/services/trackbacks/70317.html</trackback:ping><description><![CDATA[<h2><a id=viewpost1_TitleUrl href="http://www.cppblog.com/iuranus/archive/2008/12/25/70315.html"><u><font color=#800080>fopen打开文件方式</font></u></a> </h2>
<p><a href="http://www.cppblog.com/iuranus/archive/2008/12/25/70315.html">http://www.cppblog.com/iuranus/archive/2008/12/25/70315.html</a></p>
<div class=postbody><font style="COLOR: #000000; FONT-FAMILY: 宋体" face=SimHei color=#009900 size=4>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;最近写一个文件操作类，fopen的参数着实让我搞了半天，因为以前就是固定的方式读写文件的，现在要做灵活了，所以就有些参数理解不够准确。以下是关于mode参数的定义。<br><br><span style="COLOR: #000000">'r' 只读方式打开，将文件指针指向文件头，如果文件不存在，则File返回空。<br>'r+' 读写方式打开，将文件指针指向文件头，如果文件不存在，则File返回空。 <br>'w' 写入方式打开，将文件指针指向文件头并将文件大小截为零。如果文件不存在则尝试创建之。 <br>'w+' 读写方式打开，将文件指针指向文件头并将文件大小截为零。如果文件不存在则尝试创建之。 <br>'a' 写入方式打开，将文件指针指向文件末尾。如果文件不存在则尝试创建之。 <br>'a+' 读写方式打开，将文件指针指向文件末尾。如果文件不存在则尝试创建之。 <br>'x' 创建并以写入方式打开，将文件指针指向文件头。如果文件已存在，则 fopen() 调用失败并返回 FALSE。 <br>'x' 创建并以写入方式打开，将文件指针指向文件头。如果文件已存在，则 fopen() 调用失败并返回 FALSE。<br>'b' 使用字符b作为文件类型的判断，是否是binary文件。<br><br>还有在读文件时最好先判断下该文件是否存在<br>bool ClassA::IsFileExisted(const char* filePath)<br>{<br>&nbsp;&nbsp;&nbsp;struct stat info;<br>&nbsp;&nbsp;&nbsp;if(stat(filePath, &amp;info) != 0)<br>&nbsp;&nbsp;&nbsp;{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return false;<br>&nbsp;&nbsp;&nbsp;}<br>&nbsp;&nbsp;&nbsp;else<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return true;<br>}<br></span></font></div>
<img src ="http://www.cppblog.com/SpringSnow/aggbug/70317.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/SpringSnow/" target="_blank">Sandy</a> 2008-12-25 12:59 <a href="http://www.cppblog.com/SpringSnow/articles/70317.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>C++类型转换符的使用 </title><link>http://www.cppblog.com/SpringSnow/articles/67376.html</link><dc:creator>Sandy</dc:creator><author>Sandy</author><pubDate>Thu, 20 Nov 2008 05:30:00 GMT</pubDate><guid>http://www.cppblog.com/SpringSnow/articles/67376.html</guid><wfw:comment>http://www.cppblog.com/SpringSnow/comments/67376.html</wfw:comment><comments>http://www.cppblog.com/SpringSnow/articles/67376.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/SpringSnow/comments/commentRss/67376.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/SpringSnow/services/trackbacks/67376.html</trackback:ping><description><![CDATA[<p>C++的四个类型转换运算符已经有很久了,但一直没有弄清楚它们的用法,今天看到一本书上的解释,才大致地的了解了其具体的用法.</p>
<p>具体归纳如下:</p>
<p><span style="COLOR: red"><strong>reinterpret_cast</strong></span></p>
<p>该函数将一个类型的指针转换为另一个类型的指针.<br>这种转换不用修改指针变量值存放格式(不改变指针变量值),只需在编译时重新解释指针的类型就可做到.<br>reinterpret_cast 可以将指针值转换为一个整型数,但不能用于非指针类型的转换.<br>例:<br>//基本类型指针的类型转换<br>double d=9.2;<br>double* pd = &amp;d;<br>int *pi = reinterpret_cast&lt;int*&gt;(pd);&nbsp; //相当于int *pi = (int*)pd;</p>
<p>//不相关的类的指针的类型转换<br>class A{};<br>class B{};<br>A* pa = new A;<br>B* pb = reinterpret_cast&lt;B*&gt;(pa);&nbsp;&nbsp; //相当于B* pb = (B*)pa;</p>
<p>//指针转换为整数<br>long l = reinterpret_cast&lt;long&gt;(pi);&nbsp;&nbsp; //相当于long l = (long)pi;</p>
<p><br><span style="COLOR: red"><strong>const_cast</strong></span></p>
<p>该函数用于去除指针变量的常量属性，将它转换为一个对应指针类型的普通变量。反过来，也可以将一个非常量的指针变量转换为一个常指针变量。<br>这种转换是在编译期间做出的类型更改。<br>例：<br>const int* pci = 0;<br>int* pk = const_cast&lt;int*&gt;(pci);&nbsp; //相当于int* pk = (int*)pci;</p>
<p>const A* pca = new A;<br>A* pa = const_cast&lt;A*&gt;(pca);&nbsp;&nbsp;&nbsp;&nbsp; //相当于A* pa = (A*)pca;</p>
<p>出于安全性考虑，const_cast无法将非指针的常量转换为普通变量。</p>
<p><br><span style="COLOR: red"><strong>static_cast</strong></span></p>
<p>该函数主要用于基本类型之间和具有继承关系的类型之间的转换。<br>这种转换一般会更改变量的内部表示方式，因此，static_cast应用于指针类型转换没有太大意义。<br>例：<br>//基本类型转换<br>int i=0;<br>double d = static_cast&lt;double&gt;(i);&nbsp; //相当于 double d = (double)i;</p>
<p>//转换继承类的对象为基类对象<br>class Base{};<br>class Derived : public Base{};<br>Derived d;<br>Base b = static_cast&lt;Base&gt;(d);&nbsp;&nbsp;&nbsp;&nbsp; //相当于 Base b = (Base)d;</p>
<p><br><span style="COLOR: red"><strong>dynamic_cast</strong></span></p>
<p>它与static_cast相对，是动态转换。<br>这种转换是在运行时进行转换分析的，并非在编译时进行，明显区别于上面三个类型转换操作。<br>该函数只能在继承类对象的指针之间或引用之间进行类型转换。进行转换时，会根据当前运行时类型信息，判断类型对象之间的转换是否合法。dynamic_cast的指针转换失败，可通过是否为null检测，引用转换失败则抛出一个bad_cast异常。<br>例：<br>class Base{};<br>class Derived : public Base{};</p>
<p>//派生类指针转换为基类指针<br>Derived *pd = new Derived;<br>Base *pb = dynamic_cast&lt;Base*&gt;(pd);</p>
<p>if (!pb)<br>&nbsp;cout &lt;&lt; "类型转换失败" &lt;&lt; endl;</p>
<p>//没有继承关系，但被转换类有虚函数<br>class A(virtual ~A();)&nbsp;&nbsp; //有虚函数<br>class B{}:<br>A* pa = new A;<br>B* pb&nbsp; = dynamic_cast&lt;B*&gt;(pa);</p>
<p>如果对无继承关系或者没有虚函数的对象指针进行转换、基本类型指针转换以及基类指针转换为派生类指针，都不能通过编译。</p>
<img src ="http://www.cppblog.com/SpringSnow/aggbug/67376.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/SpringSnow/" target="_blank">Sandy</a> 2008-11-20 13:30 <a href="http://www.cppblog.com/SpringSnow/articles/67376.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>转：使应用程序只能运行一个实例   </title><link>http://www.cppblog.com/SpringSnow/articles/65791.html</link><dc:creator>Sandy</dc:creator><author>Sandy</author><pubDate>Sun, 02 Nov 2008 14:37:00 GMT</pubDate><guid>http://www.cppblog.com/SpringSnow/articles/65791.html</guid><wfw:comment>http://www.cppblog.com/SpringSnow/comments/65791.html</wfw:comment><comments>http://www.cppblog.com/SpringSnow/articles/65791.html#Feedback</comments><slash:comments>3</slash:comments><wfw:commentRss>http://www.cppblog.com/SpringSnow/comments/commentRss/65791.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/SpringSnow/services/trackbacks/65791.html</trackback:ping><description><![CDATA[转自：<br><a href="http://topic.csdn.net/t/20020917/11/1030014.html">http://topic.csdn.net/t/20020917/11/1030014.html</a><br>使应用程序只能运行一个实例 &nbsp; <br>&nbsp; &nbsp; &nbsp; <br>&nbsp; &nbsp; &nbsp; &nbsp; Windows是多进程操作系统，框架生成的应用程序可以多次运行，形成多个运行实例。 &nbsp; <br>&nbsp; 但在有些情况下为保证应用程序的安全运行，要求程序只能运行一个实例，比如程 &nbsp; &nbsp; <br>&nbsp; 序要使用只能被一个进程单独使用的特殊硬件（例如调制解调器）时，必须限制程 &nbsp; &nbsp; <br>&nbsp; 序只运行一个实例。 &nbsp; <br>&nbsp; &nbsp; <br>&nbsp; 这里涉及两个基本的问题，一是在程序的第二个实例启动时，如何发现该程序已有 &nbsp; &nbsp; <br>&nbsp; 一个实例在运行，而是如何将第一个实例激活，而第二个实例退出。 &nbsp; <br>&nbsp; &nbsp; <br>&nbsp; 对于第一个问题，可以通过给应用程序设置信号量，实例启动时首先检测该信号量， &nbsp; &nbsp; <br>&nbsp; 如已存在，则说明程序已运行一个实例。 &nbsp; <br>&nbsp; &nbsp; <br>&nbsp; 第二个问题的难点是获取第一个实例的主窗对象指针或句柄，然后便可用 &nbsp; &nbsp; <br>&nbsp; SetForegroundWindow来激活。虽然FindWindow函数能寻找正运行着的窗口，但该函 &nbsp; &nbsp; <br>&nbsp; 数要求指明所寻找窗口的标题或窗口类名，不是实现通用方法的途径。 &nbsp; <br>&nbsp; 我们可以用Win32 &nbsp; SDK函数SetProp来给应用程序主窗设置一个特有的标记。 &nbsp; <br>&nbsp; 用GetDesktopWindow &nbsp; 可以获取Windows系统主控窗口对象指针或句柄，所有应用程 &nbsp; <br>&nbsp; 序主窗都可看成该窗口的子窗口，即可用GetWindow函数来获得它们的对象指针或句 &nbsp; <br>&nbsp; 柄。用Win32 &nbsp; SDK函数GetProp查找每一应用程序主窗是否包含有我们设置的特定标 &nbsp; <br>&nbsp; 记便可确定它是否我们要寻找的第一个实例主窗。使第二个实例退出很简单，只要 &nbsp; <br>&nbsp; 让其应用程序对象的InitInstance函数返回FALSE即可。此外，当主窗口退出时，应 &nbsp; <br>&nbsp; 用RemoveProp函数删除我们为其设置的标记。 &nbsp; <br>&nbsp; &nbsp; <br>&nbsp; 下面的InitInstance、OnCreate和OnDestroy函数代码将实现上述的操作： &nbsp; <br>&nbsp; &nbsp; <br>&nbsp; BOOL &nbsp; CEllipseWndApp::InitInstance() &nbsp; &nbsp; <br>&nbsp; { &nbsp; &nbsp; <br>&nbsp; // &nbsp; 用应用程序名创建信号量 &nbsp; &nbsp; <br>&nbsp; HANDLE &nbsp; hSem &nbsp; = &nbsp; CreateSemaphore(NULL, &nbsp; 1, &nbsp; 1, &nbsp; m_pszExeName); &nbsp; &nbsp; <br>&nbsp; &nbsp; <br>&nbsp; // &nbsp; 信号量已存在？ &nbsp; &nbsp; <br>&nbsp; // &nbsp; 信号量存在，则程序已有一个实例运行 &nbsp; &nbsp; <br>&nbsp; if &nbsp; (GetLastError() &nbsp; == &nbsp; ERROR_ALREADY_EXISTS) &nbsp; &nbsp; <br>&nbsp; { &nbsp; &nbsp; <br>&nbsp; // &nbsp; 关闭信号量句柄 &nbsp; &nbsp; <br>&nbsp; CloseHandle(hSem); &nbsp; &nbsp; <br>&nbsp; // &nbsp; 寻找先前实例的主窗口 &nbsp; &nbsp; <br>&nbsp; HWND &nbsp; hWndPrevious &nbsp; = &nbsp; ::GetWindow(::GetDesktopWindow(),GW_CHILD); &nbsp; &nbsp; <br>&nbsp; while &nbsp; (::IsWindow(hWndPrevious)) &nbsp; &nbsp; <br>&nbsp; { &nbsp; &nbsp; <br>&nbsp; // &nbsp; 检查窗口是否有预设的标记? &nbsp; &nbsp; <br>&nbsp; // &nbsp; 有，则是我们寻找的主窗 &nbsp; &nbsp; <br>&nbsp; if &nbsp; (::GetProp(hWndPrevious, &nbsp; m_pszExeName)) &nbsp; &nbsp; <br>&nbsp; { &nbsp; &nbsp; <br>&nbsp; // &nbsp; 主窗口已最小化，则恢复其大小 &nbsp; &nbsp; <br>&nbsp; if &nbsp; (::IsIconic(hWndPrevious)) &nbsp; &nbsp; <br>&nbsp; ::ShowWindow(hWndPrevious,SW_RESTORE); &nbsp; &nbsp; <br>&nbsp; &nbsp; <br>&nbsp; // &nbsp; 将主窗激活 &nbsp; &nbsp; <br>&nbsp; ::SetForegroundWindow(hWndPrevious); &nbsp; &nbsp; <br>&nbsp; &nbsp; <br>&nbsp; // &nbsp; 将主窗的对话框激活 &nbsp; &nbsp; <br>&nbsp; ::SetForegroundWindow( &nbsp; &nbsp; <br>&nbsp; ::GetLastActivePopup(hWndPrevious)); &nbsp; &nbsp; <br>&nbsp; &nbsp; <br>&nbsp; // &nbsp; 退出本实例 &nbsp; &nbsp; <br>&nbsp; return &nbsp; FALSE; &nbsp; &nbsp; <br>&nbsp; } &nbsp; &nbsp; <br>&nbsp; // &nbsp; 继续寻找下一个窗口 &nbsp; &nbsp; <br>&nbsp; hWndPrevious &nbsp; = &nbsp; ::GetWindow(hWndPrevious,GW_HWNDNEXT); &nbsp; <br>&nbsp; } &nbsp; &nbsp; <br>&nbsp; // &nbsp; 前一实例已存在，但找不到其主窗 &nbsp; &nbsp; <br>&nbsp; // &nbsp; 可能出错了 &nbsp; &nbsp; <br>&nbsp; // &nbsp; 退出本实例 &nbsp; &nbsp; <br>&nbsp; return &nbsp; FALSE; &nbsp; &nbsp; <br>&nbsp; } &nbsp; &nbsp; <br>&nbsp; AfxEnableControlContainer(); &nbsp; &nbsp; <br>&nbsp; // &nbsp; Standard &nbsp; initialization &nbsp; &nbsp; <br>&nbsp; // &nbsp; If &nbsp; you &nbsp; are &nbsp; not &nbsp; using &nbsp; these &nbsp; features &nbsp; and &nbsp; wish &nbsp; to &nbsp; reduce &nbsp; the &nbsp; size &nbsp; &nbsp; <br>&nbsp; // &nbsp; of &nbsp; your &nbsp; final &nbsp; executable, &nbsp; you &nbsp; should &nbsp; remove &nbsp; from &nbsp; the &nbsp; following &nbsp; &nbsp; <br>&nbsp; // &nbsp; the &nbsp; specific &nbsp; initialization &nbsp; routines &nbsp; you &nbsp; do &nbsp; not &nbsp; need. &nbsp; &nbsp; <br>&nbsp; #ifdef &nbsp; _AFXDLL &nbsp; &nbsp; <br>&nbsp; Enable3dControls(); &nbsp; // &nbsp; Call &nbsp; this &nbsp; when &nbsp; using &nbsp; MFC &nbsp; in &nbsp; a &nbsp; shared &nbsp; DLL &nbsp; &nbsp; <br>&nbsp; #else &nbsp; &nbsp; <br>&nbsp; Enable3dControlsStatic();// &nbsp; Call &nbsp; this &nbsp; when &nbsp; linking &nbsp; to &nbsp; MFC &nbsp; statically &nbsp; &nbsp; <br>&nbsp; #endif &nbsp; &nbsp; <br>&nbsp; &nbsp; <br>&nbsp; CEllipseWndDlg &nbsp; dlg; &nbsp; &nbsp; <br>&nbsp; m_pMainWnd &nbsp; = &nbsp; &amp;dlg; &nbsp; &nbsp; <br>&nbsp; int &nbsp; nResponse &nbsp; = &nbsp; dlg.DoModal(); &nbsp; &nbsp; <br>&nbsp; if &nbsp; (nResponse &nbsp; == &nbsp; IDOK) &nbsp; &nbsp; <br>&nbsp; { &nbsp; &nbsp; <br>&nbsp; // &nbsp; TODO: &nbsp; Place &nbsp; code &nbsp; here &nbsp; to &nbsp; handle &nbsp; when &nbsp; the &nbsp; dialog &nbsp; is &nbsp; &nbsp; <br>&nbsp; // &nbsp; dismissed &nbsp; with &nbsp; OK &nbsp; &nbsp; <br>&nbsp; } &nbsp; &nbsp; <br>&nbsp; else &nbsp; if &nbsp; (nResponse &nbsp; == &nbsp; IDCANCEL) &nbsp; &nbsp; <br>&nbsp; { &nbsp; &nbsp; <br>&nbsp; // &nbsp; TODO: &nbsp; Place &nbsp; code &nbsp; here &nbsp; to &nbsp; handle &nbsp; when &nbsp; the &nbsp; dialog &nbsp; is &nbsp; &nbsp; <br>&nbsp; // &nbsp; dismissed &nbsp; with &nbsp; Cancel &nbsp; &nbsp; <br>&nbsp; } &nbsp; &nbsp; <br>&nbsp; // &nbsp; Since &nbsp; the &nbsp; dialog &nbsp; has &nbsp; been &nbsp; closed, &nbsp; return &nbsp; FALSE &nbsp; so &nbsp; that &nbsp; we &nbsp; exit &nbsp; the &nbsp; &nbsp; <br>&nbsp; // &nbsp; application, &nbsp; rather &nbsp; than &nbsp; start &nbsp; the &nbsp; application's &nbsp; message &nbsp; pump. &nbsp; &nbsp; <br>&nbsp; return &nbsp; FALSE; &nbsp; &nbsp; <br>&nbsp; } &nbsp; &nbsp; <br>&nbsp; &nbsp; <br>&nbsp; int &nbsp; CEllipseWndDlg::OnCreate(LPCREATESTRUCT &nbsp; lpCreateStruct) &nbsp; &nbsp; &nbsp; <br>&nbsp; { &nbsp; &nbsp; <br>&nbsp; if &nbsp; (CDialog::OnCreate(lpCreateStruct) &nbsp; == &nbsp; -1) &nbsp; &nbsp; <br>&nbsp; return &nbsp; -1; &nbsp; &nbsp; <br>&nbsp; // &nbsp; 设置寻找标记 &nbsp; &nbsp; <br>&nbsp; ::SetProp(m_hWnd, &nbsp; AfxGetApp()-&gt;m_pszExeName, &nbsp; (HANDLE)1); &nbsp; &nbsp; <br>&nbsp; return &nbsp; 0; &nbsp; &nbsp; <br>&nbsp; } &nbsp; &nbsp; <br>&nbsp; &nbsp; <br>&nbsp; void &nbsp; CEllipseWndDlg::OnDestroy() &nbsp; &nbsp; &nbsp; <br>&nbsp; { &nbsp; &nbsp; <br>&nbsp; CDialog::OnDestroy(); &nbsp; &nbsp; <br>&nbsp; // &nbsp; 删除寻找标记 &nbsp; &nbsp; <br>&nbsp; ::RemoveProp(m_hWnd, &nbsp; AfxGetApp()-&gt;m_pszExeName); &nbsp; &nbsp; &nbsp; <br>&nbsp; } &nbsp; <br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <br>&nbsp; &nbsp; <br>&nbsp; 对以上代码的补充： &nbsp; &nbsp; <br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; 查看代码和VC的帮助后，发现问题在于原文在创建信号量和设置寻找标记时使用 &nbsp; <br>&nbsp; 的是 &nbsp; CWinApp &nbsp; 的成员变量 &nbsp; m_pszExeName，该成员变量其实是应用程序执行文件的名 &nbsp; <br>&nbsp; 称去掉扩展名后的部分，而不是应用程序名。 &nbsp; &nbsp; <br>&nbsp; &nbsp; <br>&nbsp; 真正的应用程序名应为成员变量 &nbsp; m_pszAppName, &nbsp; &nbsp; <br>&nbsp; 于是将用到m_pszExeName的三处代码均改为m_pszAppName，重新编译执行，情况消失。 &nbsp; &nbsp; <br>&nbsp; &nbsp; <br>&nbsp; 最后再提供一个方法和一个信息： &nbsp; &nbsp; <br>&nbsp; &nbsp; <br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; 另一种使应用程序只能运行一个实例的方法，只需在InitInstance()的最开始添 &nbsp; <br>&nbsp; 加下列语句即可： &nbsp; &nbsp; <br>&nbsp; HANDLE &nbsp; m_hMutex=CreateMutex(NULL,TRUE, &nbsp; m_pszAppName); &nbsp; &nbsp; <br>&nbsp; if(GetLastError()==ERROR_ALREADY_EXISTS) &nbsp; { &nbsp; return &nbsp; FALSE; &nbsp; } &nbsp; <br>&nbsp; &nbsp; <br>&nbsp; 但这种方法的不足之处是不能将已经启动的实例激活。 &nbsp; &nbsp; <br>&nbsp; &nbsp; <br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; 在stingray公司整理开发的MFCFAQ软件中也提供了一些方法。该软件实际是一个 &nbsp; <br>&nbsp; MFC使用技巧的大汇集，对使用MFC极有帮助，各位朋友不妨去stingray公司的主页下 &nbsp; &nbsp; <br>&nbsp; 载。&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp; 
<img src ="http://www.cppblog.com/SpringSnow/aggbug/65791.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/SpringSnow/" target="_blank">Sandy</a> 2008-11-02 22:37 <a href="http://www.cppblog.com/SpringSnow/articles/65791.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>转：深入浅出Win32多线程设计之MFC的多线程(作者：宋宝华)</title><link>http://www.cppblog.com/SpringSnow/articles/65790.html</link><dc:creator>Sandy</dc:creator><author>Sandy</author><pubDate>Sun, 02 Nov 2008 14:36:00 GMT</pubDate><guid>http://www.cppblog.com/SpringSnow/articles/65790.html</guid><wfw:comment>http://www.cppblog.com/SpringSnow/comments/65790.html</wfw:comment><comments>http://www.cppblog.com/SpringSnow/articles/65790.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/SpringSnow/comments/commentRss/65790.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/SpringSnow/services/trackbacks/65790.html</trackback:ping><description><![CDATA[<div class=simpleblank>转自：<a href="http://blog.ednchina.com/hotpower/174823/message.aspx">http://blog.ednchina.com/hotpower/174823/message.aspx</a><a title=察看所有博客精华文章 href="http://blog.ednchina.com/cool.aspx"><u><font color=#0000ff>&nbsp;</font></u></a></div>
<div class=Text>
<p class=Label>标签： 无标签</p>
<h1><a href="http://blog.ednchina.com/hotpower/174823/message.aspx#"><u><font color=#800080>深入浅出Win32多线程设计之MFC的多线程(作者：宋宝华)</font></u></a></h1>
<p>1、创建和终止线程<br><br>　　在MFC<a class=bluekey href="http://dev.yesky.com/" target=_blank><font color=#255e9a><u>程序</u></font></a>中创建一个线程，宜调用AfxBeginThread函数。该函数因参数不同而具有两种重载版本，分别对应工作者线程和用户接口（UI）线程。<br><br>　　工作者线程<br><br>
<table borderColor=#cccccc width="90%" align=center bgColor=#e3e3e3 border=1>
    <tbody>
        <tr>
            <td>CWinThread *AfxBeginThread(<br>　AFX_THREADPROC pfnThreadProc, //控制函数<br>　LPVOID pParam, //传递给控制函数的参数<br>　int nPriority = THREAD_PRIORITY_NORMAL, //线程的优先级<br>　UINT nStackSize = 0, //线程的堆栈大小<br>　DWORD dwCreateFlags = 0, //线程的创建标志<br>　LPSECURITY_ATTRIBUTES lpSecurityAttrs = NULL //线程的<a class=bluekey href="http://soft.yesky.com/security/" target=_blank><font color=#255e9a><u>安全</u></font></a>属性<br>);</td>
        </tr>
    </tbody>
</table>
<br>　　工作者线程编程较为简单，只需编写线程控制函数和启动线程即可。下面的代码给出了定义一个控制函数和启动它的过程：<br><br>
<table borderColor=#cccccc width="90%" align=center bgColor=#e3e3e3 border=1>
    <tbody>
        <tr>
            <td>//线程控制函数<br>UINT MfcThreadProc(LPVOID lpParam)<br>{<br>　CExampleClass *lpObject = (CExampleClass*)lpParam;<br>　if (lpObject == NULL || !lpObject-&gt;IsKindof(RUNTIME_CLASS(CExampleClass)))<br>　　return - 1; //输入参数非法 <br>　//线程成功启动<br>　while (1)<br>　<br>　return 0;<br>}<br><br>//在MFC程序中启动线程<br>AfxBeginThread(MfcThreadProc, lpObject);</td>
        </tr>
    </tbody>
</table>
<br>　　UI线程<br><br>　　创建用户界面线程时，必须首先从CWinThread 派生类，并使用 DECLARE_DYNCREATE 和 IMPLEMENT_DYNCREATE 宏声明此类。<br><br>　　下面给出了CWinThread类的原型（添加了关于其重要函数功能和是否需要被继承类重载的注释）：<br><br>
<table borderColor=#cccccc width="90%" align=center bgColor=#e3e3e3 border=1>
    <tbody>
        <tr>
            <td>class CWinThread : public CCmdTarget<br>{<br>　DECLARE_DYNAMIC(CWinThread)<br><br>　public:<br>　　// Constructors<br>　　CWinThread();<br>　　BOOL CreateThread(DWORD dwCreateFlags = 0, UINT nStackSize = 0,<br>LPSECURITY_ATTRIBUTES lpSecurityAttrs = NULL);<br><br>　　// Attributes<br>　　CWnd* m_pMainWnd; // main window (usually same AfxGetApp()-&gt;m_pMainWnd)<br>　　CWnd* m_pActiveWnd; // active main window (may not be m_pMainWnd)<br>　　BOOL m_bAutoDelete; // enables 'delete this' after thread termination<br><br>　　// only valid while running<br>　　HANDLE m_hThread; // this thread's HANDLE<br>　　operator HANDLE() const;<br>　　DWORD m_nThreadID; // this thread's ID<br><br>　　int GetThreadPriority();<br>　　BOOL SetThreadPriority(int nPriority);<br><br>　　// Operations<br>　　DWORD SuspendThread();<br>　　DWORD ResumeThread();<br>　　BOOL PostThreadMessage(UINT message, WPARAM wParam, LPARAM lParam);<br><br>　　// Overridables<br>　　//执行线程实例初始化，必须重写<br>　　virtual BOOL InitInstance();<br><br>　　// running and idle processing<br>　　//控制线程的函数，包含消息泵，一般不重写<br>　　virtual int Run();<br><br>　　//消息调度到TranslateMessage和DispatchMessage之前对其进行筛选，<br>　　//通常不重写<br>　　virtual BOOL PreTranslateMessage(MSG* pMsg);<br><br>　　virtual BOOL PumpMessage(); // low level message pump<br><br>　　//执行线程特定的闲置时间处理，通常不重写<br>　　virtual BOOL OnIdle(LONG lCount); // return TRUE if more idle processing<br>　　virtual BOOL IsIdleMessage(MSG* pMsg); // checks for special messages<br><br>　　//线程终止时执行清除，通常需要重写<br>　　virtual int ExitInstance(); // default will 'delete this'<br><br>　　//截获由线程的消息和命令处理程序引发的未处理异常，通常不重写<br>　　virtual LRESULT ProcessWndProcException(CException* e, const MSG* pMsg);<br><br>　　// Advanced: handling messages sent to message filter hook<br>　　virtual BOOL ProcessMessageFilter(int code, LPMSG lpMsg);<br><br>　　// Advanced: virtual access to m_pMainWnd<br>　　virtual CWnd* GetMainWnd();<br><br>　　// Implementation<br>　public:<br>　　virtual ~CWinThread();<br>　　#ifdef _DEBUG<br>　　　virtual void AssertValid() const;<br>　　　virtual void Dump(CDumpContext&amp; dc) const;<br>　　　int m_nDisablePumpCount; // Diagnostic trap to detect illegal re-entrancy<br>　　#endif<br>　　void CommonConstruct();<br>　　virtual void Delete();<br>　　// 'delete this' only if m_bAutoDelete == TRUE<br><br>　　// message pump for Run<br>　　MSG m_msgCur; // current message<br><br>　public:<br>　　// constructor used by implementation of AfxBeginThread<br>　　CWinThread(AFX_THREADPROC pfnThreadProc, LPVOID pParam);<br><br>　　// valid after construction<br>　　LPVOID m_pThreadParams; // generic parameters passed to starting function<br>　　AFX_THREADPROC m_pfnThreadProc;<br><br>　　// set after OLE is initialized<br>　　void (AFXAPI* m_lpfnOleTermOrFreeLib)(BOOL, BOOL);<br>　　COleMessageFilter* m_pMessageFilter;<br><br>　protected:<br>　　CPoint m_ptCursorLast; // last mouse position<br>　　UINT m_nMsgLast; // last mouse message<br>　　BOOL DispatchThreadMessageEx(MSG* msg); // helper<br>　　void DispatchThreadMessage(MSG* msg); // obsolete<br>};<br></td>
        </tr>
    </tbody>
</table>
<br>　　启动UI线程的AfxBeginThread函数的原型为：<br><br>
<table borderColor=#cccccc width="90%" align=center bgColor=#e3e3e3 border=1>
    <tbody>
        <tr>
            <td>CWinThread *AfxBeginThread(<br>　//从CWinThread派生的类的 RUNTIME_CLASS<br>　CRuntimeClass *pThreadClass, <br>　int nPriority = THREAD_PRIORITY_NORMAL, <br>　UINT nStackSize = 0, <br>　DWORD dwCreateFlags = 0,<br>　LPSECURITY_ATTRIBUTES lpSecurityAttrs = NULL<br>); </td>
        </tr>
    </tbody>
</table>
<br>　　我们可以方便地使用VC++ 6.0类向导定义一个继承自CWinThread的用户线程类。下面给出产生我们自定义的CWinThread子类CMyUIThread的方法。<br><br>　　打开VC++ 6.0类向导，在如下窗口中选择Base Class类为CWinThread，输入子类名为CMyUIThread，点击"OK"按钮后就产生了类CMyUIThread。<br><br>
<table width="90%" align=center border=0>
    <tbody>
        <tr>
            <td>
            <div align=center><img src="http://dev.yesky.com/imagelist/06/01/575l04oac886.jpg" border=0></div>
            </td>
        </tr>
    </tbody>
</table>
<br>　　其源代码框架为：<br><br>
<table borderColor=#cccccc width="90%" align=center bgColor=#e3e3e3 border=1>
    <tbody>
        <tr>
            <td>/////////////////////////////////////////////////////////////////////////////<br>// CMyUIThread thread<br><br>class CMyUIThread : public CWinThread<br>{<br>　DECLARE_DYNCREATE(CMyUIThread)<br>　protected:<br>　　CMyUIThread(); // protected constructor used by dynamic creation<br><br>　　// Attributes<br>　public:<br><br>　　// Operations<br>　public:<br><br>　　// Overrides<br>　　// ClassWizard generated virtual function overrides<br>　　//{{AFX_VIRTUAL(CMyUIThread)<br>　　public:<br>　　　virtual BOOL InitInstance();<br>　　　virtual int ExitInstance();<br>　　//}}AFX_VIRTUAL<br><br>　　// Implementation<br>　protected:<br>　　virtual ~CMyUIThread();<br><br>　　// Generated message map functions<br>　　//{{AFX_MSG(CMyUIThread)<br>　　　// NOTE - the ClassWizard will add and remove member functions here.<br>　　//}}AFX_MSG<br><br>　DECLARE_MESSAGE_MAP()<br>};<br><br>/////////////////////////////////////////////////////////////////////////////<br>// CMyUIThread<br><br>IMPLEMENT_DYNCREATE(CMyUIThread, CWinThread)<br><br>CMyUIThread::CMyUIThread()<br>{}<br><br>CMyUIThread::~CMyUIThread()<br>{}<br><br>BOOL CMyUIThread::InitInstance()<br>{<br>　// TODO: perform and per-thread initialization here<br>　return TRUE;<br>}<br><br>int CMyUIThread::ExitInstance()<br>{<br>　// TODO: perform any per-thread cleanup here<br>　return CWinThread::ExitInstance();<br>}<br><br>BEGIN_MESSAGE_MAP(CMyUIThread, CWinThread)<br>//{{AFX_MSG_MAP(CMyUIThread)<br>// NOTE - the ClassWizard will add and remove mapping macros here.<br>//}}AFX_MSG_MAP<br>END_MESSAGE_MAP()</td>
        </tr>
    </tbody>
</table>
<br>　　使用下列代码就可以启动这个UI线程：<br><br>
<table borderColor=#cccccc width="90%" align=center bgColor=#e3e3e3 border=1>
    <tbody>
        <tr>
            <td>CMyUIThread *pThread;<br>pThread = (CMyUIThread*)<br>AfxBeginThread( RUNTIME_CLASS(CMyUIThread) );</td>
        </tr>
    </tbody>
</table>
<br>　　另外，我们也可以不用AfxBeginThread 创建线程，而是分如下两步完成：<br><br>　　（1）调用线程类的构造函数创建一个线程对象；<br><br>　　（2）调用CWinThread::CreateThread函数来启动该线程。<br><br>　　在线程自身内调用AfxEndThread函数可以终止该线程：<br><br>
<table borderColor=#cccccc width="90%" align=center bgColor=#e3e3e3 border=1>
    <tbody>
        <tr>
            <td>void AfxEndThread(<br>　UINT nExitCode //the exit code of the thread<br>);</td>
        </tr>
    </tbody>
</table>
<p><br>　　对于UI线程而言，如果消息队列中放入了WM_QUIT消息，将结束线程。<br><br>　　关于UI线程和工作者线程的分配，最好的做法是：将所有与UI相关的操作放入主线程，其它的纯粹的运算工作交给独立的数个工作者线程。<br><br>　　候捷先生早些时间喜欢为MDI程序的每个窗口创建一个线程，他后来澄清了这个错误。因为如果为MDI程序的每个窗口都单独创建一个线程，在窗口进行切换的时候，将进行线程的上下文切换！</p>
<p>2.线程间通信<br><br>　　MFC中定义了继承自CSyncObject类的CCriticalSection 、CCEvent、CMutex、CSemaphore类封装和简化了WIN32 API所提供的临界区、事件、互斥和信号量。使用这些同步机制，必须包含"Afxmt.h"头文件。下图给出了类的继承关系：<br><br>
<table width="90%" align=center border=0>
    <tbody>
        <tr>
            <td>
            <div align=center><img src="http://dev.yesky.com/imagelist/06/01/1pzf0afz2ais.jpg" border=0></div>
            </td>
        </tr>
    </tbody>
</table>
<br>　　作为CSyncObject类的继承类，我们仅仅使用基类CSyncObject的接口函数就可以方便、统一的操作CCriticalSection 、CCEvent、CMutex、CSemaphore类，下面是CSyncObject类的原型：<br><br>
<table borderColor=#cccccc width="90%" align=center bgColor=#e3e3e3 border=1>
    <tbody>
        <tr>
            <td>class CSyncObject : public CObject<br>{<br>　DECLARE_DYNAMIC(CSyncObject)<br><br>　// Constructor<br>　public:<br>　　CSyncObject(LPCTSTR pstrName);<br><br>　　// Attributes<br>　public:<br>　　operator HANDLE() const;<br>　　HANDLE m_hObject;<br><br>　　// Operations<br>　　virtual BOOL Lock(DWORD dwTimeout = INFINITE);<br>　　virtual BOOL Unlock() = 0;<br>　　virtual BOOL Unlock(LONG /* lCount */, LPLONG /* lpPrevCount="NULL" */)<br>　　{ return TRUE; }<br><br>　　// Implementation<br>　public:<br>　　virtual ~CSyncObject();<br>　　#ifdef _DEBUG<br>　　　CString m_strName;<br>　　　virtual void AssertValid() const;<br>　　　virtual void Dump(CDumpContext&amp; dc) const;<br>　　#endif<br>　　friend class CSingleLock;<br>　　friend class CMultiLock;<br>};</td>
        </tr>
    </tbody>
</table>
<br>　　CSyncObject类最主要的两个函数是Lock和Unlock，若我们直接使用CSyncObject类及其派生类，我们需要非常小心地在Lock之后调用Unlock。<br><br>　　MFC提供的另两个类CSingleLock（等待一个对象）和CMultiLock（等待多个对象）为我们编写应用程序提供了更灵活的机制，下面以实际来阐述CSingleLock的用法：<br><br>
<table borderColor=#cccccc width="90%" align=center bgColor=#e3e3e3 border=1>
    <tbody>
        <tr>
            <td>class CThreadSafeWnd<br><br>　　~CThreadSafeWnd(){}<br>　　void SetWindow(CWnd *pwnd)<br>　　{<br>　　　m_pCWnd = pwnd;<br>　　}<br>　　void PaintBall(COLORREF color, CRect &amp;rc);<br>　private:<br>　　CWnd *m_pCWnd;<br>　　CCriticalSection m_CSect;<br>};<br><br>void CThreadSafeWnd::PaintBall(COLORREF color, CRect &amp;rc)<br>{<br>　CSingleLock csl(&amp;m_CSect);<br>　//缺省的Timeout是INFINITE，只有m_Csect被激活，csl.Lock()才能返回<br>　//true，这里一直等待<br>　if (csl.Lock())<br>;<br>　{<br>　　// not necessary<br>　　//AFX_MANAGE_STATE(AfxGetStaticModuleState( ));<br>　　CDC *pdc = m_pCWnd-&gt;GetDC();<br>　　CBrush brush(color);<br>　　CBrush *oldbrush = pdc-&gt;SelectObject(&amp;brush);<br>　　pdc-&gt;Ellipse(rc);<br>　　pdc-&gt;SelectObject(oldbrush);<br>　　GdiFlush(); // don't wait to update the display<br>　}<br>}</td>
        </tr>
    </tbody>
</table>
<br>　　上述实例讲述了用CSingleLock对Windows GDI相关对象进行保护的方法，下面再给出一个其他方面的例子：<br><br>
<table borderColor=#cccccc width="90%" align=center bgColor=#e3e3e3 border=1>
    <tbody>
        <tr>
            <td>int array1[10], array2[10];<br>CMutexSection section; //创建一个CMutex类的对象<br><br>//赋值线程控制函数<br>UINT EvaluateThread(LPVOID param) <br>{<br>　CSingleLock singlelock; <br>　singlelock(&amp;section);<br><br>　//互斥区域<br>　singlelock.Lock();<br>　for (int i = 0; i &lt; 10; i++)<br>　　array1[i] = i;<br>　singlelock.Unlock();<br>}<br>//拷贝线程控制函数<br>UINT CopyThread(LPVOID param) <br>{<br>　CSingleLock singlelock;<br>　singlelock(&amp;section);<br><br>　//互斥区域<br>　singlelock.Lock();<br>　for (int i = 0; i &lt; 10; i++)<br>　　array2[i] = array1[i];<br>　singlelock.Unlock();<br>}<br>}<br><br>AfxBeginThread(EvaluateThread, NULL); //启动赋值线程<br>AfxBeginThread(CopyThread, NULL); //启动拷贝线程</td>
        </tr>
    </tbody>
</table>
<br>　　上面的例子中启动了两个线程EvaluateThread和CopyThread，线程EvaluateThread把10个数赋值给数组array1[]，线程CopyThread将数组array1[]拷贝给数组array2[]。由于数组的拷贝和赋值都是整体行为，如果不以互斥形式执行代码段：<br><br>
<table borderColor=#cccccc width="90%" align=center bgColor=#e3e3e3 border=1>
    <tbody>
        <tr>
            <td>for (int i = 0; i &lt; 10; i++)<br>array1[i] = i;</td>
        </tr>
    </tbody>
</table>
<br>　　和<br><br>
<table borderColor=#cccccc width="90%" align=center bgColor=#e3e3e3 border=1>
    <tbody>
        <tr>
            <td>for (int i = 0; i &lt; 10; i++)<br>array2[i] = array1[i];</td>
        </tr>
    </tbody>
</table>
<br>　　其结果是很难预料的！<br><br>　　除了可使用CCriticalSection、CEvent、CMutex、CSemaphore作为线程间同步通信的方式以外，我们还可以利用PostThreadMessage函数在线程间发送消息：<br><br>
<table borderColor=#cccccc width="90%" align=center bgColor=#e3e3e3 border=1>
    <tbody>
        <tr>
            <td>BOOL PostThreadMessage(DWORD idThread, // thread identifier<br>UINT Msg, // message to post<br>WPARAM wParam, // first message parameter<br>LPARAM lParam // second message parameter<br></td>
        </tr>
    </tbody>
</table>
</p>
<p>&nbsp;</p>
<p>3.线程与消息队列<br><br>　　在WIN32中，每一个线程都对应着一个消息队列。由于一个线程可以产生数个窗口，所以并不是每个窗口都对应着一个消息队列。下列几句话应该作为"定理"被记住：<br><br>　　"定理" 一<br><br>　　所有产生给某个窗口的消息，都先由创建这个窗口的线程处理；<br><br>　　"定理" 二<br><br>　　Windows屏幕上的每一个控件都是一个窗口，有对应的窗口函数。<br><br>　　消息的发送通常有两种方式，一是SendMessage，一是PostMessage，其原型分别为：<br><br>
<table borderColor=#cccccc width="90%" align=center bgColor=#e3e3e3 border=1>
    <tbody>
        <tr>
            <td>LRESULT SendMessage(HWND hWnd, // handle of destination window<br>　UINT Msg, // message to send<br>　WPARAM wParam, // first message parameter<br>　LPARAM lParam // second message parameter<br>);<br>BOOL PostMessage(HWND hWnd, // handle of destination window<br>　UINT Msg, // message to post<br>　WPARAM wParam, // first message parameter<br>　LPARAM lParam // second message parameter<br>);</td>
        </tr>
    </tbody>
</table>
<br>　　两个函数原型中的四个参数的意义相同，但是SendMessage和PostMessage的行为有差异。SendMessage必须等待消息被处理后才返回，而PostMessage仅仅将消息放入消息队列。SendMessage的目标窗口如果属于另一个线程，则会发生线程上下文切换，等待另一线程处理完成消息。为了防止另一线程当掉，导致SendMessage永远不能返回，我们可以调用SendMessageTimeout函数：<br><br>
<table borderColor=#cccccc width="90%" align=center bgColor=#e3e3e3 border=1>
    <tbody>
        <tr>
            <td>LRESULT SendMessageTimeout(<br>　HWND hWnd, // handle of destination window<br>　UINT Msg, // message to send<br>　WPARAM wParam, // first message parameter<br>　LPARAM lParam, // second message parameter<br>　UINT fuFlags, // how to send the message<br>　UINT uTimeout, // time-out duration<br>　LPDWORD lpdwResult // return value for synchronous call<br>);</td>
        </tr>
    </tbody>
</table>
<br>　　4. MFC线程、消息队列与MFC程序的"生死因果"<br><br>　　分析MFC程序的主线程启动及消息队列处理的过程将有助于我们进一步理解UI线程与消息队列的关系，为此我们需要简单地叙述一下MFC程序的"生死因果"（侯捷：《深入浅出MFC》）。<br><br>　　使用VC++ 6.0的向导完成一个最简单的单文档架构MFC应用程序MFCThread：<br><br>　　（1） 输入MFC EXE工程名MFCThread；<br><br>　　（2） 选择单文档架构，不支持Document/View结构；<br><br>　　（3） ActiveX、3D container等其他选项都选择无。<br><br>　　我们来分析这个工程。下面是产生的核心源代码：<br><br>　　MFCThread.h 文件<br><br>
<table borderColor=#cccccc width="90%" align=center bgColor=#e3e3e3 border=1>
    <tbody>
        <tr>
            <td>class CMFCThreadApp : public CWinApp<br>{<br>　public:<br>　　CMFCThreadApp();<br><br>　　// Overrides<br>　　// ClassWizard generated virtual function overrides<br>　　//{{AFX_VIRTUAL(CMFCThreadApp)<br>　　　public:<br>　　　　virtual BOOL InitInstance();<br>　　//}}AFX_VIRTUAL<br><br>　　// Implementation<br><br>　public:<br>　　//{{AFX_MSG(CMFCThreadApp)<br>　　　afx_msg void OnAppAbout();<br>　　　// NOTE - the ClassWizard will add and remove member functions here.<br>　　　// DO NOT EDIT what you see in these blocks of generated code !<br>　　//}}AFX_MSG<br>　DECLARE_MESSAGE_MAP()<br>};</td>
        </tr>
    </tbody>
</table>
<br>　　MFCThread.cpp文件<br><br>
<table borderColor=#cccccc width="90%" align=center bgColor=#e3e3e3 border=1>
    <tbody>
        <tr>
            <td>CMFCThreadApp theApp;<br><br>/////////////////////////////////////////////////////////////////////////////<br>// CMFCThreadApp initialization<br><br>BOOL CMFCThreadApp::InitInstance()<br>{<br>　&#8230;<br>　CMainFrame* pFrame = new CMainFrame;<br>　m_pMainWnd = pFrame;<br><br>　// create and load the frame with its resources<br>　pFrame-&gt;LoadFrame(IDR_MAINFRAME,WS_OVERLAPPEDWINDOW | FWS_ADDTOTITLE, NULL,NULL);<br>　// The one and only window has been initialized, so show and update it.<br>　pFrame-&gt;ShowWindow(SW_SHOW);<br>　pFrame-&gt;UpdateWindow();<br><br>　return TRUE;<br>}</td>
        </tr>
    </tbody>
</table>
<br>　　MainFrm.h文件<br><br>
<table borderColor=#cccccc width="90%" align=center bgColor=#e3e3e3 border=1>
    <tbody>
        <tr>
            <td>#include "ChildView.h"<br><br>class CMainFrame : public CFrameWnd<br>{<br>　public:<br>　　CMainFrame();<br>　protected: <br>　　DECLARE_DYNAMIC(CMainFrame)<br><br>　　// Attributes<br>　public:<br><br>　　// Operations<br>　public:<br>　　// Overrides<br>　　// ClassWizard generated virtual function overrides<br>　　//{{AFX_VIRTUAL(CMainFrame)<br>　　　virtual BOOL PreCreateWindow(CREATESTRUCT&amp; cs);<br>　　　virtual BOOL OnCmdMsg(UINT nID, int nCode, void* pExtra, AFX_CMDHANDLERINFO* pHandlerInfo);<br>　　//}}AFX_VIRTUAL<br><br>　　// Implementation<br>　public:<br>　　virtual ~CMainFrame();<br>　　#ifdef _DEBUG<br>　　　virtual void AssertValid() const;<br>　　　virtual void Dump(CDumpContext&amp; dc) const;<br>　　#endif<br>　　CChildView m_wndView;<br><br>　　// Generated message map functions<br>　protected:<br>　//{{AFX_MSG(CMainFrame)<br>　　afx_msg void OnSetFocus(CWnd *pOldWnd);<br>　　// NOTE - the ClassWizard will add and remove member functions here.<br>　　// DO NOT EDIT what you see in these blocks of generated code!<br>　//}}AFX_MSG<br>　DECLARE_MESSAGE_MAP()<br>};</td>
        </tr>
    </tbody>
</table>
<br>　　MainFrm.cpp文件<br><br>
<table borderColor=#cccccc width="90%" align=center bgColor=#e3e3e3 border=1>
    <tbody>
        <tr>
            <td>IMPLEMENT_DYNAMIC(CMainFrame, CFrameWnd)<br><br>BEGIN_MESSAGE_MAP(CMainFrame, CFrameWnd)<br>　//{{AFX_MSG_MAP(CMainFrame)<br>　　// NOTE - the ClassWizard will add and remove mapping macros here.<br>　　// DO NOT EDIT what you see in these blocks of generated code !<br>　　ON_WM_SETFOCUS()<br>　//}}AFX_MSG_MAP<br>END_MESSAGE_MAP()<br><br>/////////////////////////////////////////////////////////////////////////////<br>// CMainFrame construction/destruction<br><br>CMainFrame::CMainFrame()<br>{<br>　// TODO: add member initialization code here<br>}<br><br>CMainFrame::~CMainFrame()<br>{}<br><br>BOOL CMainFrame::PreCreateWindow(CREATESTRUCT&amp; cs)<br>{<br>　if( !CFrameWnd::PreCreateWindow(cs) )<br>　　return FALSE;<br>　　// TODO: Modify the Window class or styles here by modifying<br>　　// the CREATESTRUCT cs<br><br>　cs.dwExStyle &amp;= ~WS_EX_CLIENTEDGE;<br>　cs.lpszClass = AfxRegisterWndClass(0);<br>　return TRUE;<br>}</td>
        </tr>
    </tbody>
</table>
<br>　　ChildView.h文件<br><br>
<table borderColor=#cccccc width="90%" align=center bgColor=#e3e3e3 border=1>
    <tbody>
        <tr>
            <td>// CChildView window<br><br>class CChildView : public CWnd<br>{<br>　// Construction<br>　public:<br>　　CChildView();<br><br>　　// Attributes<br>　public:<br>　　// Operations<br>　public:<br>　　// Overrides<br>　　// ClassWizard generated virtual function overrides<br>　　//{{AFX_VIRTUAL(CChildView)<br>　　　protected:<br>　　　　virtual BOOL PreCreateWindow(CREATESTRUCT&amp; cs);<br>　　//}}AFX_VIRTUAL<br><br>　　// Implementation<br>　public:<br>　　virtual ~CChildView();<br><br>　　// Generated message map functions<br>　protected:<br>　　//{{AFX_MSG(CChildView)<br>　　　afx_msg void OnPaint();<br>　　//}}AFX_MSG<br>　DECLARE_MESSAGE_MAP()<br>};<br><br>ChildView.cpp文件<br>// CChildView<br><br>CChildView::CChildView()<br>{}<br><br>CChildView::~CChildView()<br>{}<br><br>BEGIN_MESSAGE_MAP(CChildView,CWnd )<br>//}AFX_MSG_MAP<br>END_MESSAGE_MAP()<br><br>/////////////////////////////////////////////////////////////////////////////<br>// CChildView message handlers<br><br>BOOL CChildView::PreCreateWindow(CREATESTRUCT&amp; cs) <br>{<br>　if (!CWnd::PreCreateWindow(cs))<br>　　return FALSE;<br><br>　cs.dwExStyle |= WS_EX_CLIENTEDGE;<br>　cs.style &amp;= ~WS_BORDER;<br>　cs.lpszClass = AfxRegisterWndClass(CS_HREDRAW|CS_VREDRAW|CS_DBLCLKS,::LoadCursor(NULL, IDC_ARROW),<br>HBRUSH(COLOR_WINDOW+1),NULL);<br><br>　return TRUE;<br>}<br><br>void CChildView::OnPaint() <br>{<br>　CPaintDC dc(this); // device context for painting<br><br>　// TODO: Add your message handler code here<br>　// Do not call CWnd::OnPaint() for painting messages<br>}</td>
        </tr>
    </tbody>
</table>
<br>　　文件MFCThread.h和MFCThread.cpp定义和实现的类CMFCThreadApp继承自CWinApp类，而CWinApp类又继承自CWinThread类（CWinThread类又继承自CCmdTarget类），所以CMFCThread本质上是一个MFC线程类，下图给出了相关的类层次结构：<br><br>
<table width="90%" align=center border=0>
    <tbody>
        <tr>
            <td>
            <div align=center><img src="http://dev.yesky.com/imagelist/06/01/g705u0arabo5.jpg" border=0></div>
            </td>
        </tr>
    </tbody>
</table>
</p>
<p>&nbsp;</p>
<p>我们提取CWinApp类原型的一部分：<br><br>
<table borderColor=#cccccc width="90%" align=center bgColor=#e3e3e3 border=1>
    <tbody>
        <tr>
            <td>class CWinApp : public CWinThread<br>{<br>　DECLARE_DYNAMIC(CWinApp)<br>　public:<br>　　// Constructor<br>　　CWinApp(LPCTSTR lpszAppName = NULL);// default app name<br>　　// Attributes<br>　　// Startup args (do not change)<br>　　HINSTANCE m_hInstance;<br>　　HINSTANCE m_hPrevInstance;<br>　　LPTSTR m_lpCmdLine;<br>　　int m_nCmdShow;<br>　　// Running args (can be changed in InitInstance)<br>　　LPCTSTR m_pszAppName; // human readable name<br>　　LPCTSTR m_pszExeName; // executable name (no spaces)<br>　　LPCTSTR m_pszHelpFilePath; // default based on module path<br>　　LPCTSTR m_pszProfileName; // default based on app name<br><br>　　// Overridables<br>　　virtual BOOL InitApplication();<br>　　virtual BOOL InitInstance();<br>　　virtual int ExitInstance(); // return app exit code<br>　　virtual int Run();<br>　　virtual BOOL OnIdle(LONG lCount); // return TRUE if more idle processing<br>　　virtual LRESULT ProcessWndProcException(CException* e,const MSG* pMsg);<br><br>　public:<br>　　virtual ~CWinApp();<br>　protected:<br>　　DECLARE_MESSAGE_MAP()<br>};</td>
        </tr>
    </tbody>
</table>
<br>　　SDK程序的WinMain 所完成的工作现在由CWinApp 的三个函数完成：<br><br>
<table borderColor=#cccccc width="90%" align=center bgColor=#e3e3e3 border=1>
    <tbody>
        <tr>
            <td>virtual BOOL InitApplication();<br>virtual BOOL InitInstance();<br>virtual int Run();</td>
        </tr>
    </tbody>
</table>
<br>　　"CMFCThreadApp theApp;"语句定义的全局变量theApp是整个程式的application object，每一个MFC 应用程序都有一个。当我们执行MFCThread程序的时候，这个全局变量被构造。theApp 配置完成后，WinMain开始执行。但是程序中并没有WinMain的代码，它在哪里呢？原来MFC早已准备好并由Linker直接加到应用程序代码中的，其原型为（存在于VC++6.0安装目录下提供的APPMODUL.CPP文件中）：<br><br>
<table borderColor=#cccccc width="90%" align=center bgColor=#e3e3e3 border=1>
    <tbody>
        <tr>
            <td>extern "C" int WINAPI<br>_tWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,<br>LPTSTR lpCmdLine, int nCmdShow)<br>{<br>　// call shared/exported WinMain<br>　return AfxWinMain(hInstance, hPrevInstance, lpCmdLine, nCmdShow);<br>}</td>
        </tr>
    </tbody>
</table>
<br>　　其中调用的AfxWinMain如下（存在于VC++6.0安装目录下提供的WINMAIN.CPP文件中）：<br><br>
<table borderColor=#cccccc width="90%" align=center bgColor=#e3e3e3 border=1>
    <tbody>
        <tr>
            <td>int AFXAPI AfxWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,<br>LPTSTR lpCmdLine, int nCmdShow)<br>{<br>　ASSERT(hPrevInstance == NULL);<br><br>　int nReturnCode = -1;<br>　CWinThread* pThread = AfxGetThread();<br>　CWinApp* pApp = AfxGetApp();<br><br>　// AFX internal initialization<br>　if (!AfxWinInit(hInstance, hPrevInstance, lpCmdLine, nCmdShow))<br>　　goto InitFailure;<br><br>　// App global initializations (rare)<br>　if (pApp != NULL &amp;&amp; !pApp-&gt;InitApplication())<br>　　goto InitFailure;<br><br>　// Perform specific initializations<br>　if (!pThread-&gt;InitInstance())<br>　{<br>　　if (pThread-&gt;m_pMainWnd != NULL)<br>　　{<br>　　　TRACE0("Warning: Destroying non-NULL m_pMainWndn");<br>　　　pThread-&gt;m_pMainWnd-&gt;DestroyWindow();<br>　　}<br>　　nReturnCode = pThread-&gt;ExitInstance();<br>　　goto InitFailure;<br>　}<br>　nReturnCode = pThread-&gt;Run();<br><br>　InitFailure:<br>　#ifdef _DEBUG<br>　　// Check for missing AfxLockTempMap calls<br>　　if (AfxGetModuleThreadState()-&gt;m_nTempMapLock != 0)<br>　　{<br>　　　TRACE1("Warning: Temp map lock count non-zero (%ld).n",<br>AfxGetModuleThreadState()-&gt;m_nTempMapLock);<br>　　}<br>　　AfxLockTempMaps();<br>　　AfxUnlockTempMaps(-1);<br>　#endif<br><br>　AfxWinTerm();<br>　return nReturnCode;<br>}</td>
        </tr>
    </tbody>
</table>
<br>　　我们提取主干，实际上，这个函数做的事情主要是：<br><br>
<table borderColor=#cccccc width="90%" align=center bgColor=#e3e3e3 border=1>
    <tbody>
        <tr>
            <td>CWinThread* pThread = AfxGetThread();<br>CWinApp* pApp = AfxGetApp();<br>AfxWinInit(hInstance, hPrevInstance, lpCmdLine, nCmdShow)<br>pApp-&gt;InitApplication()<br>pThread-&gt;InitInstance()<br>pThread-&gt;Run();</td>
        </tr>
    </tbody>
</table>
<br>　　其中，InitApplication 是注册窗口类别的场所；InitInstance是产生窗口并显示窗口的场所；Run是提取并分派消息的场所。这样，MFC就同WIN32 SDK程序对应起来了。CWinThread::Run是程序生命的"活水源头"（侯捷：《深入浅出MFC》，函数存在于VC++ 6.0安装目录下提供的THRDCORE.CPP文件中）：<br><br>
<table borderColor=#cccccc width="90%" align=center bgColor=#e3e3e3 border=1>
    <tbody>
        <tr>
            <td>// main running routine until thread exits<br>int CWinThread::Run()<br>{<br>　ASSERT_VALID(this);<br><br>　// for tracking the idle time state<br>　BOOL bIdle = TRUE;<br>　LONG lIdleCount = 0;<br><br>　// acquire and dispatch messages until a WM_QUIT message is received.<br>　for (;;)<br>　{<br>　　// phase1: check to see if we can do idle work<br>　　while (bIdle &amp;&amp; !::PeekMessage(&amp;m_msgCur, NULL, NULL, NULL, PM_NOREMOVE))<br>　　{<br>　　　// call OnIdle while in bIdle state<br>　　　if (!OnIdle(lIdleCount++))<br>　　　　bIdle = FALSE; // assume "no idle" state<br>　　}<br><br>　　// phase2: pump messages while available<br>　　do<br>　　{<br>　　　// pump message, but quit on WM_QUIT<br>　　　if (!PumpMessage())<br>　　　　return ExitInstance();<br><br>　　　// reset "no idle" state after pumping "normal" message<br>　　　if (IsIdleMessage(&amp;m_msgCur))<br>　　　{<br>　　　　bIdle = TRUE;<br>　　　　lIdleCount = 0;<br>　　　}<br><br>　　} while (::PeekMessage(&amp;m_msgCur, NULL, NULL, NULL, PM_NOREMOVE));<br>　}<br>　ASSERT(FALSE); // not reachable<br>}</td>
        </tr>
    </tbody>
</table>
<br>　　其中的PumpMessage函数又对应于：<br><br>
<table borderColor=#cccccc width="90%" align=center bgColor=#e3e3e3 border=1>
    <tbody>
        <tr>
            <td>/////////////////////////////////////////////////////////////////////////////<br>// CWinThread implementation helpers<br><br>BOOL CWinThread::PumpMessage()<br>{<br>　ASSERT_VALID(this);<br><br>　if (!::GetMessage(&amp;m_msgCur, NULL, NULL, NULL))<br>　{<br>　　return FALSE;<br>　}<br><br>　// process this message<br>　if(m_msgCur.message != WM_KICKIDLE &amp;&amp; !PreTranslateMessage(&amp;m_msgCur))<br>　<br>　return TRUE;<br>}</td>
        </tr>
    </tbody>
</table>
<br>　　因此，忽略IDLE状态，整个RUN的执行提取主干就是：<br><br>
<table borderColor=#cccccc width="90%" align=center bgColor=#e3e3e3 border=1>
    <tbody>
        <tr>
            <td>do while (::PeekMessage(...));</td>
        </tr>
    </tbody>
</table>
<br>　　由此，我们建立了MFC消息获取和派生机制与WIN32 SDK程序之间的对应关系。下面继续分析MFC消息的"绕行"过程。<br><br>　　在MFC中，只要是CWnd 衍生类别，就可以拦下任何Windows消息。与窗口无关的MFC类别（例如CDocument 和CWinApp）如果也想处理消息，必须衍生自CCmdTarget，并且只可能收到WM_COMMAND消息。所有能进行MESSAGE_MAP的类都继承自CCmdTarget，如：<br><br>
<table width="90%" align=center border=0>
    <tbody>
        <tr>
            <td>
            <div align=center><img src="http://dev.yesky.com/imagelist/06/01/4bidfnchk995.jpg" border=0><img src="http://dev.yesky.com/imagelist/06/01/eqq6knx8w7z0.jpg" border=0><img src="http://dev.yesky.com/imagelist/06/01/m7pbiwap6un1.jpg" border=0></div>
            </td>
        </tr>
    </tbody>
</table>
<br>　　MFC中MESSAGE_MAP的定义依赖于以下三个宏：<br><br>
<table borderColor=#cccccc width="90%" align=center bgColor=#e3e3e3 border=1>
    <tbody>
        <tr>
            <td>DECLARE_MESSAGE_MAP()<br><br>BEGIN_MESSAGE_MAP( <br>　theClass, //Specifies the name of the class whose message map this is<br>　baseClass //Specifies the name of the base class of theClass<br>)<br><br>END_MESSAGE_MAP()</td>
        </tr>
    </tbody>
</table>
<br>　　我们程序中涉及到的有：MFCThread.h、MainFrm.h、ChildView.h文件<br><br>
<table borderColor=#cccccc width="90%" align=center bgColor=#e3e3e3 border=1>
    <tbody>
        <tr>
            <td>DECLARE_MESSAGE_MAP()<br>MFCThread.cpp文件<br>BEGIN_MESSAGE_MAP(CMFCThreadApp, CWinApp)<br>//{{AFX_MSG_MAP(CMFCThreadApp)<br>ON_COMMAND(ID_APP_ABOUT, OnAppAbout)<br>// NOTE - the ClassWizard will add and remove mapping macros here.<br>// DO NOT EDIT what you see in these blocks of generated code!<br>//}}AFX_MSG_MAP<br>END_MESSAGE_MAP()<br>MainFrm.cpp文件<br>BEGIN_MESSAGE_MAP(CMainFrame, CFrameWnd)<br>//{{AFX_MSG_MAP(CMainFrame)<br>// NOTE - the ClassWizard will add and remove mapping macros here.<br>// DO NOT EDIT what you see in these blocks of generated code !<br>ON_WM_SETFOCUS()<br>//}}AFX_MSG_MAP<br>END_MESSAGE_MAP()<br>ChildView.cpp文件<br>BEGIN_MESSAGE_MAP(CChildView,CWnd )<br>//}AFX_MSG_MAP<br>END_MESSAGE_MAP()</td>
        </tr>
    </tbody>
</table>
<br>　　由这些宏，MFC建立了一个消息映射表（消息流动网），按照消息流动网匹配对应的消息处理函数，完成整个消息的"绕行"。<br><br>　　看到这里相信你有这样的疑问：程序定义了CWinApp类的theApp全局变量，可是从来没有调用AfxBeginThread或theApp.CreateThread启动线程呀，theApp对应的线程是怎么启动的？<br><br>　　答：MFC在这里用了很高明的一招。实际上，程序开始运行，第一个线程是由<a class=bluekey href="http://os.yesky.com/" target=_blank><font color=#255e9a><u>操作系统</u></font></a>（OS）启动的，在CWinApp的构造函数里，MFC将theApp"对应"向了这个线程，具体的实现是这样的：<br><br>
<table borderColor=#cccccc width="90%" align=center bgColor=#e3e3e3 border=1>
    <tbody>
        <tr>
            <td>CWinApp::CWinApp(LPCTSTR lpszAppName)<br>{<br>　if (lpszAppName != NULL)<br>　　m_pszAppName = _tcsdup(lpszAppName);<br>　else<br>　　m_pszAppName = NULL;<br><br>　// initialize CWinThread state<br>　AFX_MODULE_STATE *pModuleState = _AFX_CMDTARGET_GETSTATE();<br>　AFX_MODULE_THREAD_STATE *pThreadState = pModuleState-&gt;m_thread;<br>　ASSERT(AfxGetThread() == NULL);<br>　pThreadState-&gt;m_pCurrentWinThread = this;<br>　ASSERT(AfxGetThread() == this);<br>　m_hThread = ::GetCurrentThread();<br>　m_nThreadID = ::GetCurrentThreadId();<br><br>　// initialize CWinApp state<br>　ASSERT(afxCurrentWinApp == NULL); // only one CWinApp object please<br>　pModuleState-&gt;m_pCurrentWinApp = this;<br>　ASSERT(AfxGetApp() == this);<br><br>　// in non-running state until WinMain<br>　m_hInstance = NULL;<br>　m_pszHelpFilePath = NULL;<br>　m_pszProfileName = NULL;<br>　m_pszRegistryKey = NULL;<br>　m_pszExeName = NULL;<br>　m_pRecentFileList = NULL;<br>　m_pDocManager = NULL;<br>　m_atomApp = m_atomSystemTopic = NULL; //微软懒鬼？或者他认为 <br>　//这样连等含义更明确？<br>　m_lpCmdLine = NULL;<br>　m_pCmdInfo = NULL;<br><br>　// initialize wait cursor state<br>　m_nWaitCursorCount = 0;<br>　m_hcurWaitCursorRestore = NULL;<br><br>　// initialize current printer state<br>　m_hDevMode = NULL;<br>　m_hDevNames = NULL;<br>　m_nNumPreviewPages = 0; // not specified (defaults to 1)<br><br>　// initialize DAO state<br>　m_lpfnDaoTerm = NULL; // will be set if AfxDaoInit called<br><br>　// other initialization<br>　m_bHelpMode = FALSE;<br>　m_nSafetyPoolSize = 512; // default size<br>}</td>
        </tr>
    </tbody>
</table>
<br>　　很显然，theApp成员变量都被赋予OS启动的这个当前线程相关的值，如代码：<br><br>
<table borderColor=#cccccc width="90%" align=center bgColor=#e3e3e3 border=1>
    <tbody>
        <tr>
            <td>m_hThread = ::GetCurrentThread();//theApp的线程句柄等于当前线程句柄 <br>m_nThreadID = ::GetCurrentThreadId();//theApp的线程ID等于当前线程ID</td>
        </tr>
    </tbody>
</table>
<br>　　所以CWinApp类几乎只是为MFC程序的第一个线程量身定制的，它不需要也不能被AfxBeginThread或theApp.CreateThread"再次"启动。这就是CWinApp类和theApp全局变量的内涵！如果你要再增加一个UI线程，不要继承类CWinApp，而应继承类CWinThread。而参考第1节，由于我们一般以主线程（在MFC程序里实际上就是OS启动的第一个线程）处理所有窗口的消息，所以我们几乎没有再启动UI线程的需求！</p>
</div>
<img src ="http://www.cppblog.com/SpringSnow/aggbug/65790.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/SpringSnow/" target="_blank">Sandy</a> 2008-11-02 22:36 <a href="http://www.cppblog.com/SpringSnow/articles/65790.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>转：Windows多线程多任务设计初步 </title><link>http://www.cppblog.com/SpringSnow/articles/65789.html</link><dc:creator>Sandy</dc:creator><author>Sandy</author><pubDate>Sun, 02 Nov 2008 14:35:00 GMT</pubDate><guid>http://www.cppblog.com/SpringSnow/articles/65789.html</guid><wfw:comment>http://www.cppblog.com/SpringSnow/comments/65789.html</wfw:comment><comments>http://www.cppblog.com/SpringSnow/articles/65789.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/SpringSnow/comments/commentRss/65789.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/SpringSnow/services/trackbacks/65789.html</trackback:ping><description><![CDATA[转自：<a href="http://www.cppblog.com/ivenher/articles/983.html">http://www.cppblog.com/ivenher/articles/983.html</a><br>［前言：］当前流行的Windows操作系统，它能同时运行几个程序(独立运行的程序又称之为进程)，对于同一个程序，它又可以分成若干个独立的执行流，我们称之为线程，线程提供了多任务处理的能力。用进程和线程的观点来研究软件是当今普遍采用的方法，进程和线程的概念的出现，对提高软件的并行性有着重要的意义。现在的应用软件无一不是多线程多任务处理，单线城的软件是不可想象的。因此掌握多线程多任务设计方法对每个程序员都是必需要掌握的。本文针对多线程技术在应用中经常遇到的问题，如线程间的通信、同步等，对它们分别进行探讨。 <br><br>　　 一、 理解线程 <br><br>　　 要讲解线程，不得不说一下进程，进程是应用程序的执行实例，每个进程是由私有的虚拟地址空间、代码、数据和其它系统资源组成。进程在运行时创建的资源随着进程的终止而死亡。线程的基本思想很简单，它是一个独立的执行流，是进程内部的一个独立的执行单元，相当于一个子程序，它对应Visual C++中的CwinThread类的对象。单独一个执行程序运行时，缺省的运行包含的一个主线程，主线程以函数地址的形式，如main或WinMain函数，提供程序的启动点，当主线程终止时，进程也随之终止，但根据需要，应用程序又可以分解成许多独立执行的线程，每个线程并行的运行在同一进程中。 <br><br>　　 一个进程中的所有线程都在该进程的虚拟地址空间中，使用该进程的全局变量和系统资源。操作系统给每个线程分配不同的CPU时间片，在某一个时刻，CPU只执行一个时间片内的线程，多个时间片中的相应线程在CPU内轮流执行，由于每个时间片时间很短，所以对用户来说，仿佛各个线程在计算机中是并行处理的。操作系统是根据线程的优先级来安排CPU的时间，优先级高的线程优先运行，优先级低的线程则继续等待。 <br><br>　　 线程被分为两种：用户界面线程和工作线程（又称为后台线程）。用户界面线程通常用来处理用户的输入并响应各种事件和消息，其实，应用程序的主执行线程CWinAPP对象就是一个用户界面线程，当应用程序启动时自动创建和启动，同样它的终止也意味着该程序的结束，进城终止。工作者线程用来执行程序的后台处理任务，比如计算、调度、对串口的读写操作等，它和用户界面线程的区别是它不用从CwinThread类派生来创建，对它来说最重要的是如何实现工作线程任务的运行控制函数。工作线程和用户界面线程启动时要调用同一个函数的不同版本；最后需要读者明白的是，一个进程中的所有线程共享它们父进程的变量，但同时每个线程可以拥有自己的变量。 <br>　　 二、 线程的管理和操作 <br><br>　　 1． 线程的启动 <br><br>　　 创建一个用户界面线程，首先要从类CwinThread产生一个派生类，同时必须使用DECLARE_DYNCREATE和IMPLEMENT_DYNCREATE来声明和实现这个CwinThread派生类。 <br><br>　　 第二步是根据需要重载该派生类的一些成员函数如：ExitInstance()；InitInstance()；OnIdle();PreTranslateMessage()等函数，最后启动该用户界面线程，调用AfxBeginThread()函数的一个版本：CWinThread* AfxBeginThread( CRuntimeClass* pThreadClass, int nPriority = THREAD_PRIORITY_NORMAL, UINT nStackSize = 0, DWORD dwCreateFlags = 0, LPSECURITY_ATTRIBUTES lpSecurityAttrs = NULL );其中第一个参数为指向定义的用户界面线程类指针变量，第二个参数为线程的优先级，第三个参数为线程所对应的堆栈大小，第四个参数为线程创建时的附加标志，缺省为正常状态，如为CREATE_SUSPENDED则线程启动后为挂起状态。 <br><br>　　 对于工作线程来说，启动一个线程，首先需要编写一个希望与应用程序的其余部分并行运行的函数如Fun1()，接着定义一个指向CwinThread对象的指针变量*pThread,调用AfxBeginThread(Fun1,param,priority)函数，返回值付给pThread变量的同时一并启动该线程来执行上面的Fun1()函数，其中Fun1是线程要运行的函数的名字，也既是上面所说的控制函数的名字，param是准备传送给线程函数Fun1的任意32位值，priority则是定义该线程的优先级别，它是预定义的常数，读者可参考MSDN。 <br><br>　　 2．线程的优先级 <br><br>　　 以下的CwinThread类的成员函数用于线程优先级的操作： <br><br>int GetThreadPriority(); <br>BOOL SetThradPriority()(int nPriority); <br><br>上述的二个函数分别用来获取和设置线程的优先级，这里的优先级，是相对于该线程所处的优先权层次而言的，处于同一优先权层次的线程，优先级高的线程先运行；处于不同优先权层次上的线程，谁的优先权层次高，谁先运行。至于优先级设置所需的常数，自己参考MSDN就可以了，要注意的是要想设置线程的优先级，这个线程在创建时必须具有THREAD_SET_INFORMATION访问权限。对于线程的优先权层次的设置，CwinThread类没有提供相应的函数，但是可以通过Win32 SDK函数GetPriorityClass()和SetPriorityClass()来实现。 <br><br>　　 3．线程的悬挂、恢复 <br><br>　　 CwinThread类中包含了应用程序悬挂和恢复它所创建的线程的函数，其中SuspendThread()用来悬挂线程，暂停线程的执行；ResumeThread()用来恢复线程的执行。如果你对一个线程连续若干次执行SuspendThread()，则需要连续执行相应次的ResumeThread()来恢复线程的运行。 <br><br>　　 4．结束线程 <br><br>　　 终止线程有三种途径，线程可以在自身内部调用AfxEndThread()来终止自身的运行；可以在线程的外部调用BOOL TerminateThread( HANDLE hThread, DWORD dwExitCode )来强行终止一个线程的运行，然后调用CloseHandle（）函数释放线程所占用的堆栈；第三种方法是改变全局变量，使线程的执行函数返回，则该线程终止。下面以第三种方法为例，给出部分代码： <br><br>//////////////////////////////////////////////////////////////// <br>//////CtestView message handlers <br>/////Set to True to end thread <br>Bool bend=FALSE;//定义的全局变量，用于控制线程的运行 <br>//The Thread Function <br>UINT ThreadFunction(LPVOID pParam)//线程函数 <br>{ <br>while(!bend) <br>{Beep(100,100); <br>Sleep(1000); <br>} <br>return 0; <br>} <br>CwinThread *pThread; <br>HWND hWnd; <br>///////////////////////////////////////////////////////////// <br>Void CtestView::OninitialUpdate() <br>{ <br>hWnd=GetSafeHwnd(); <br>pThread=AfxBeginThread(ThradFunction,hWnd);//启动线程 <br>pThread-&gt;m_bAutoDelete=FALSE;//线程为手动删除 <br>Cview::OnInitialUpdate(); <br>} <br>//////////////////////////////////////////////////////////////// <br>Void CtestView::OnDestroy() <br>{ bend=TRUE;//改变变量，线程结束 <br>WaitForSingleObject(pThread-&gt;m_hThread,INFINITE);//等待线程结束 <br>delete pThread;//删除线程 <br>Cview::OnDestroy(); <br>} <br>　　 三、 线程之间的通信 <br><br>　　 通常情况下，一个次级线程要为主线程完成某种特定类型的任务，这就隐含着表示在主线程和次级线程之间需要建立一个通信的通道。一般情况下，有下面的几种方法实现这种通信任务：使用全局变量（上一节的例子其实使用的就是这种方法）、使用事件对象、使用消息。这里我们主要介绍后两种方法。 <br><br>　　 1． 利用用户定义的消息通信 <br><br>　　 在Windows程序设计中，应用程序的每一个线程都拥有自己的消息队列，甚至工作线程也不例外，这样一来，就使得线程之间利用消息来传递信息就变的非常简单。首先用户要定义一个用户消息，如下所示：#define WM_USERMSG WMUSER+100；在需要的时候，在一个线程中调用 <br><br>：：PostMessage((HWND)param,WM_USERMSG,0,0) <br>或 <br>CwinThread::PostThradMessage() <br><br>来向另外一个线程发送这个消息，上述函数的四个参数分别是消息将要发送到的目的窗口的句柄、要发送的消息标志符、消息的参数WPARAM和LPARAM。下面的代码是对上节代码的修改，修改后的结果是在线程结束时显示一个对话框，提示线程结束： <br><br>UINT ThreadFunction(LPVOID pParam) <br>{ <br>while(!bend) <br>{ <br>Beep(100,100); <br>Sleep(1000); <br>} <br>：：PostMessage(hWnd,WM_USERMSG,0,0)； <br>return 0; <br>} <br>////////WM_USERMSG消息的响应函数为OnThreadended(WPARAM wParam,LPARAM lParam) <br>LONG CTestView::OnThreadended(WPARAM wParam,LPARAM lParam) <br>{ <br>AfxMessageBox("Thread ended."); <br>Retrun 0; <br>} <br><br>上面的例子是工作者线程向用户界面线程发送消息，对于工作者线程，如果它的设计模式也是消息驱动的，那么调用者可以向它发送初始化、退出、执行某种特定的处理等消息，让它在后台完成。在控制函数中可以直接使用：：GetMessage()这个SDK函数进行消息分检和处理，自己实现一个消息循环。GetMessage()函数在判断该线程的消息队列为空时，线程将系统分配给它的时间片让给其它线程，不无效的占用CPU的时间，如果消息队列不为空，就获取这个消息，判断这个消息的内容并进行相应的处理。 <br><br>　　 2．用事件对象实现通信 <br><br>　　 在线程之间传递信号进行通信比较复杂的方法是使用事件对象，用MFC的Cevent类的对象来表示。事件对象处于两种状态之一：有信号和无信号，线程可以监视处于有信号状态的事件，以便在适当的时候执行对事件的操作。上述例子代码修改如下： <br><br>//////////////////////////////////////////////////////////////////// <br>Cevent threadStart,threadEnd; <br>//////////////////////////////////////////////////////////////////// <br>UINT ThreadFunction(LPVOID pParam) <br>{ <br>：：WaitForSingleObject(threadStart.m_hObject,INFINITE); <br>AfxMessageBox("Thread start."); <br>while(!bend) <br>{ <br>Beep(100,100); <br>Sleep(1000); <br>Int result=::WaitforSingleObject(threadEnd.m_hObject,0); <br>//等待threadEnd事件有信号，无信号时线程在这里悬停 <br>If(result==Wait_OBJECT_0) <br>Bend=TRUE; <br>} <br>：：PostMessage(hWnd,WM_USERMSG,0,0)； <br>return 0; <br>} <br>///////////////////////////////////////////////////////////// <br>Void CtestView::OninitialUpdate() <br>{ <br>hWnd=GetSafeHwnd(); <br>threadStart.SetEvent();//threadStart事件有信号 <br>pThread=AfxBeginThread(ThreadFunction,hWnd);//启动线程 <br>pThread-&gt;m_bAutoDelete=FALSE; <br>Cview::OnInitialUpdate(); <br>} <br>//////////////////////////////////////////////////////////////// <br>Void CtestView::OnDestroy() <br>{ threadEnd.SetEvent(); <br>WaitForSingleObject(pThread-&gt;m_hThread,INFINITE); <br>delete pThread; <br>Cview::OnDestroy(); <br>} <br><br>运行这个程序，当关闭程序时，才显示提示框，显示"Thread ended" <br>　　 四、 线程之间的同步 <br><br>　　 前面我们讲过，各个线程可以访问进程中的公共变量，所以使用多线程的过程中需要注意的问题是如何防止两个或两个以上的线程同时访问同一个数据，以免破坏数据的完整性。保证各个线程可以在一起适当的协调工作称为线程之间的同步。前面一节介绍的事件对象实际上就是一种同步形式。Visual C++中使用同步类来解决操作系统的并行性而引起的数据不安全的问题，MFC支持的七个多线程的同步类可以分成两大类：同步对象（CsyncObject、Csemaphore、Cmutex、CcriticalSection和Cevent）和同步访问对象（CmultiLock和CsingleLock）。本节主要介绍临界区（critical section）、互斥（mutexe）、信号量（semaphore），这些同步对象使各个线程协调工作，程序运行起来更安全。 <br><br>　　 1． 临界区 <br><br>　　 临界区是保证在某一个时间只有一个线程可以访问数据的方法。使用它的过程中，需要给各个线程提供一个共享的临界区对象，无论哪个线程占有临界区对象，都可以访问受到保护的数据，这时候其它的线程需要等待，直到该线程释放临界区对象为止，临界区被释放后，另外的线程可以强占这个临界区，以便访问共享的数据。临界区对应着一个CcriticalSection对象，当线程需要访问保护数据时，调用临界区对象的Lock()成员函数；当对保护数据的操作完成之后，调用临界区对象的Unlock()成员函数释放对临界区对象的拥有权，以使另一个线程可以夺取临界区对象并访问受保护的数据。同时启动两个线程，它们对应的函数分别为WriteThread()和ReadThread()，用以对公共数组组array[]操作，下面的代码说明了如何使用临界区对象： <br><br>#include "afxmt.h" <br>int array[10],destarray[10]; <br>CCriticalSection Section; <br>//////////////////////////////////////////////////////////////////////// <br>UINT WriteThread(LPVOID param) <br>{Section.Lock(); <br>for(int x=0;x&lt;10;x++) <br>array[x]=x; <br>Section.Unlock(); <br>} <br>UINT ReadThread(LPVOID param) <br>{ <br>Section.Lock(); <br>For(int x=0;x&lt;10;x++) <br>Destarray[x]=array[x]; <br>Section.Unlock(); <br>} <br><br>上述代码运行的结果应该是Destarray数组中的元素分别为1-9，而不是杂乱无章的数，如果不使用同步，则不是这个结果，有兴趣的读者可以实验一下。 <br>　　 2． 互斥 <br><br>　　 互斥与临界区很相似，但是使用时相对复杂一些，它不仅可以在同一应用程序的线程间实现同步，还可以在不同的进程间实现同步，从而实现资源的安全共享。互斥与Cmutex类的对象相对应，使用互斥对象时，必须创建一个CSingleLock或CMultiLock对象，用于实际的访问控制，因为这里的例子只处理单个互斥，所以我们可以使用CSingleLock对象，该对象的Lock()函数用于占有互斥，Unlock()用于释放互斥。实现代码如下： <br><br>#include "afxmt.h" <br>int array[10],destarray[10]; <br>CMutex Section; <br><br>///////////////////////////////////////////////////////////// <br>UINT WriteThread(LPVOID param) <br>{ CsingleLock singlelock; <br>singlelock (&amp;Section); <br>singlelock.Lock(); <br>for(int x=0;x&lt;10;x++) <br>array[x]=x; <br>singlelock.Unlock(); <br>} <br>UINT ReadThread(LPVOID param) <br>{ CsingleLock singlelock; <br>singlelock (&amp;Section); <br>singlelock.Lock(); <br><br>For(int x=0;x&lt;10;x++) <br>Destarray[x]=array[x]; <br>singlelock.Unlock(); <br><br>} <br><br>　　 3． 信号量 <br><br>　　 信号量的用法和互斥的用法很相似，不同的是它可以同一时刻允许多个线程访问同一个资源，创建一个信号量需要用Csemaphore类声明一个对象，一旦创建了一个信号量对象，就可以用它来对资源的访问技术。要实现计数处理，先创建一个CsingleLock或CmltiLock对象，然后用该对象的Lock()函数减少这个信号量的计数值，Unlock()反之。下面的代码分别启动三个线程，执行时同时显示二个消息框，然后10秒后第三个消息框才得以显示。 <br><br>///////////////////////////////////////////////////////////////// <br>Csemaphore *semaphore; <br>Semaphore=new Csemaphore(2,2); <br>HWND hWnd=GetSafeHwnd(); <br>AfxBeginThread(threadProc1,hWnd); <br>AfxBeginThread(threadProc2,hWnd); <br>AfxBeginThread(threadProc3,hWnd); <br>////////////////////////////////////////////////////////////////////// <br>UINT ThreadProc1(LPVOID param) <br>{CsingleLock singelLock(semaphore); <br>singleLock.Lock(); <br>Sleep(10000); <br>::MessageBox((HWND)param,"Thread1 had access","Thread1",MB_OK); <br>return 0; <br>} <br>UINT ThreadProc2(LPVOID param) <br>{CSingleLock singelLock(semaphore); <br>singleLock.Lock(); <br>Sleep(10000); <br>::MessageBox((HWND)param,"Thread2 had access","Thread2",MB_OK); <br>return 0; <br>} <br>UINT ThreadProc3(LPVOID param) <br>{CsingleLock singelLock(semaphore); <br>singleLock.Lock(); <br>Sleep(10000); <br>::MessageBox((HWND)param,"Thread3 had access","Thread3",MB_OK); <br>return 0; <br>} <br><br><br>　　 对复杂的应用程序来说，线程的应用给应用程序提供了高效、快速、安全的数据处理能力。本文讲述了线程中经常遇到的问题，希望对读者朋友有一定的帮助。
<img src ="http://www.cppblog.com/SpringSnow/aggbug/65789.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/SpringSnow/" target="_blank">Sandy</a> 2008-11-02 22:35 <a href="http://www.cppblog.com/SpringSnow/articles/65789.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>转：一个跨平台的 C++ 内存泄漏检测器</title><link>http://www.cppblog.com/SpringSnow/articles/65742.html</link><dc:creator>Sandy</dc:creator><author>Sandy</author><pubDate>Sun, 02 Nov 2008 02:56:00 GMT</pubDate><guid>http://www.cppblog.com/SpringSnow/articles/65742.html</guid><wfw:comment>http://www.cppblog.com/SpringSnow/comments/65742.html</wfw:comment><comments>http://www.cppblog.com/SpringSnow/articles/65742.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/SpringSnow/comments/commentRss/65742.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/SpringSnow/services/trackbacks/65742.html</trackback:ping><description><![CDATA[<blockquote>
<p><a href="http://www-128.ibm.com/developerworks/cn/linux/l-mleak2/index.html">http://www-128.ibm.com/developerworks/cn/linux/l-mleak2/index.html</a></p>
<p><br>内存泄漏对于C/C++程序员来说也可以算作是个永恒的话题了吧。在Windows下，MFC的一个很有用的功能就是能在程序运行结束时报告是否发生了内存泄漏。在Linux下，相对来说就没有那么容易使用的解决方案了：像mpatrol之类的现有工具，易用性、附加开销和性能都不是很理想。本文实现一个极易于使用、跨平台的C++内存泄漏检测器。并对相关的技术问题作一下探讨。</p>
</blockquote><!--start RESERVED FOR FUTURE USE INCLUDE FILES--><!-- include java script once we verify teams wants to use this and it will work on dbcs and cyrillic characters --><!--end RESERVED FOR FUTURE USE INCLUDE FILES-->
<p><a name=1><span class=atitle>基本使用</span></a></p>
<p>
<p>对于下面这样的一个简单程序test.cpp：</p>
<table cellSpacing=0 cellPadding=0 width=600 border=0>
    <tbody>
        <tr>
            <td class=code-outline>
            <pre class=displaycode>int main()
            {
            int* p1 = new int;
            char* p2 = new char[10];
            return 0;
            }
            </pre>
            </td>
        </tr>
    </tbody>
</table>
<br>
<p>我们的基本需求当然是对于该程序报告存在两处内存泄漏。要做到这点的话，非常简单，只要把debug_new.cpp也编译、链接进去就可以了。在Linux下，我们使用：</p>
<table cellSpacing=0 cellPadding=0 width="100%" border=0>
    <tbody>
        <tr>
            <td class=code-outline>
            <pre class=displaycode>g++ test.cpp debug_new.cpp -o test
            </pre>
            </td>
        </tr>
    </tbody>
</table>
<br>
<p>输出结果如下所示：</p>
<table cellSpacing=0 cellPadding=0 width="100%" border=0>
    <tbody>
        <tr>
            <td class=code-outline>
            <pre class=displaycode>Leaked object at 0x805e438 (size 10, &lt;Unknown&gt;:0)
            Leaked object at 0x805e410 (size 4, &lt;Unknown&gt;:0)
            </pre>
            </td>
        </tr>
    </tbody>
</table>
<br>
<p>如果我们需要更清晰的报告，也很简单，在test.cpp开头加一行</p>
<table cellSpacing=0 cellPadding=0 width="100%" border=0>
    <tbody>
        <tr>
            <td class=code-outline>
            <pre class=displaycode>#include "debug_new.h"
            </pre>
            </td>
        </tr>
    </tbody>
</table>
<br>
<p>即可。添加该行后的输出如下：</p>
<table cellSpacing=0 cellPadding=0 width="100%" border=0>
    <tbody>
        <tr>
            <td class=code-outline>
            <pre class=displaycode>Leaked object at 0x805e438 (size 10, test.cpp:5)
            Leaked object at 0x805e410 (size 4, test.cpp:4)
            </pre>
            </td>
        </tr>
    </tbody>
</table>
<br>
<p>非常简单！</p>
<br>
<table cellSpacing=0 cellPadding=0 width="100%" border=0>
    <tbody>
        <tr>
            <td><img height=1 alt="" src="http://www.ibm.com/i/v14/rules/blue_rule.gif" width="100%"><br><img height=6 alt="" src="http://www.ibm.com/i/c.gif" width=8 border=0></td>
        </tr>
    </tbody>
</table>
<table class=no-print cellSpacing=0 cellPadding=0 align=right>
    <tbody>
        <tr align=right>
            <td><img height=4 alt="" src="http://www.ibm.com/i/c.gif" width="100%"><br>
            <table cellSpacing=0 cellPadding=0 border=0>
                <tbody>
                    <tr>
                        <td vAlign=center><img height=16 alt="" src="http://www.ibm.com/i/v14/icons/u_bold.gif" width=16 border=0><br></td>
                        <td vAlign=top align=right><a class=fbox href="http://www-128.ibm.com/developerworks/cn/linux/l-mleak2/index.html#main" cmImpressionSent="1"><strong><font color=#996699>回页首</font></strong></a></td>
                    </tr>
                </tbody>
            </table>
            </td>
        </tr>
    </tbody>
</table>
<br><br>
<p><a name=2><span class=atitle>背景知识</span></a></p>
<p>
<p>在new/delete操作中，C++为用户产生了对operator new和operator delete的调用。这是用户不能改变的。operator new和operator delete的原型如下所示：</p>
<table cellSpacing=0 cellPadding=0 width="100%" border=0>
    <tbody>
        <tr>
            <td class=code-outline>
            <pre class=displaycode>void *operator new(size_t) throw(std::bad_alloc);
            void *operator new[](size_t) throw(std::bad_alloc);
            void operator delete(void*) throw();
            void operator delete[](void*) throw();
            </pre>
            </td>
        </tr>
    </tbody>
</table>
<br>
<p>对于"new int"，编译器会产生一个调用"operator new(sizeof(int))"，而对于"new char[10]"，编译器会产生"operator new[](sizeof(char) * 10)"（如果new后面跟的是一个类名的话，当然还要调用该类的构造函数）。类似地，对于"delete ptr"和"delete[] ptr"，编译器会产生"operator delete(ptr)"调用和"operator delete[](ptr)"调用（如果ptr的类型是指向对象的指针的话，那在operator delete之前还要调用对象的析构函数）。当用户没有提供这些操作符时，编译系统自动提供其定义；而当用户自己提供了这些操作符时，就覆盖了编译系统提供的版本，从而可获得对动态内存分配操作的精确跟踪和控制。</p>
<p>同时，我们还可以使用placement new操作符来调整operator new的行为。所谓placement new，是指带有附加参数的new操作符，比如，当我们提供了一个原型为</p>
<table cellSpacing=0 cellPadding=0 width="100%" border=0>
    <tbody>
        <tr>
            <td class=code-outline>
            <pre class=displaycode>void* operator new(size_t size, const char* file, int line);
            </pre>
            </td>
        </tr>
    </tbody>
</table>
<br>
<p>的操作符时，我们就可以使用"new("hello", 123) int"来产生一个调用"operator new(sizeof(int), "hello", 123)"。这可以是相当灵活的。又如，C++标准要求编译器提供的一个placement new操作符是</p>
<table cellSpacing=0 cellPadding=0 width="100%" border=0>
    <tbody>
        <tr>
            <td class=code-outline>
            <pre class=displaycode>void* operator new(size_t size, const std::nothrow_t&amp;);
            </pre>
            </td>
        </tr>
    </tbody>
</table>
<br>
<p>其中，nothrow_t通常是一个空结构（定义为"struct nothrow_t {};"），其唯一目的是提供编译器一个可根据重载规则识别具体调用的类型。用户一般简单地使用"new(std::nothrow) 类型"（nothrow是一个nothrow_t类型的常量）来调用这个placement new操作符。它与标准new的区别是，new在分配内存失败时会抛出异常，而"new(std::nothrow)"在分配内存失败时会返回一个空指针。</p>
<p>要注意的是，没有对应的"delete(std::nothrow) ptr"的语法；不过后文会提到另一个相关问题。</p>
<p>要进一步了解以上关于C++语言特性的信息，请参阅[Stroustrup1997]，特别是6.2.6、10.4.11、15.6、19.4.5和B.3.4节。这些C++语言特性是理解本实现的关键。</p>
<br>
<table cellSpacing=0 cellPadding=0 width="100%" border=0>
    <tbody>
        <tr>
            <td><img height=1 alt="" src="http://www.ibm.com/i/v14/rules/blue_rule.gif" width="100%"><br><img height=6 alt="" src="http://www.ibm.com/i/c.gif" width=8 border=0></td>
        </tr>
    </tbody>
</table>
<table class=no-print cellSpacing=0 cellPadding=0 align=right>
    <tbody>
        <tr align=right>
            <td><img height=4 alt="" src="http://www.ibm.com/i/c.gif" width="100%"><br>
            <table cellSpacing=0 cellPadding=0 border=0>
                <tbody>
                    <tr>
                        <td vAlign=center><img height=16 alt="" src="http://www.ibm.com/i/v14/icons/u_bold.gif" width=16 border=0><br></td>
                        <td vAlign=top align=right><a class=fbox href="http://www-128.ibm.com/developerworks/cn/linux/l-mleak2/index.html#main" cmImpressionSent="1"><strong><font color=#996699>回页首</font></strong></a></td>
                    </tr>
                </tbody>
            </table>
            </td>
        </tr>
    </tbody>
</table>
<br><br>
<p><a name=3><span class=atitle>检测原理</span></a></p>
<p>
<p>和其它一些内存泄漏检测的方式类似，debug_new中提供了operator new重载，并使用了宏在用户程序中进行替换。debug_new.h中的相关部分如下：</p>
<table cellSpacing=0 cellPadding=0 width="100%" border=0>
    <tbody>
        <tr>
            <td class=code-outline>
            <pre class=displaycode>void* operator new(size_t size, const char* file, int line);
            void* operator new[](size_t size, const char* file, int line);
            #define new DEBUG_NEW
            #define DEBUG_NEW new(__FILE__, __LINE__)
            </pre>
            </td>
        </tr>
    </tbody>
</table>
<br>
<p>拿上面加入debug_new.h包含后的test.cpp来说，"new char[10]"在预处理后会变成"new("test.cpp", 4) char[10]"，编译器会据此产生一个"operator new[](sizeof(char) * 10, "test.cpp", 4)"调用。通过在debug_new.cpp中自定义"operator new(size_t, const char*, int)"和"operator delete(void*)"（以及"operator new[]&#8230;"和"operator delete[]&#8230;"；为避免行文累赘，以下不特别指出，说到operator new和operator delete均同时包含数组版本），我可以跟踪所有的内存分配调用，并在指定的检查点上对不匹配的new和delete操作进行报警。实现可以相当简单，用map记录所有分配的内存指针就可以了：new时往map里加一个指针及其对应的信息，delete时删除指针及对应的信息；delete时如果map里不存在该指针为错误删除；程序退出时如果map里还存在未删除的指针则说明有内存泄漏。</p>
<p>不过，如果不包含debug_new.h，这种方法就起不了作用了。不仅如此，部分文件包含debug_new.h，部分不包含debug_new.h都是不可行的。因为虽然我们使用了两种不同的operator new --"operator new(size_t, const char*, int)"和"operator new(size_t)"-- 但可用的"operator delete"还是只有一种！使用我们自定义的"operator delete"，当我们删除由"operator new(size_t)"分配的指针时，程序将认为被删除的是一个非法指针！我们处于一个两难境地：要么对这种情况产生误报，要么对重复删除同一指针两次不予报警：都不是可接受的良好行为。</p>
<p>看来，自定义全局"operator new(size_t)"也是不可避免的了。在debug_new中，我是这样做的：</p>
<table cellSpacing=0 cellPadding=0 width="100%" border=0>
    <tbody>
        <tr>
            <td class=code-outline>
            <pre class=displaycode>void* operator new(size_t size)
            {
            return operator new(size, "&lt;Unknown&gt;", 0);
            }
            </pre>
            </td>
        </tr>
    </tbody>
</table>
<br>
<p>但前面描述的方式去实现内存泄漏检测器，在某些C++的实现中（如GCC 2.95.3中带的SGI STL）工作正常，但在另外一些实现中会莫名其妙地崩溃。原因也不复杂，SGI STL使用了内存池，一次分配一大片内存，因而使利用map成为可能；但在其他的实现可能没这样做，在map中添加数据会调用operator new，而operator new会在map中添加数据，从而构成一个死循环，导致内存溢出，应用程序立即崩溃。因此，我们不得不停止使用方便的STL模板，而使用手工构建的数据结构：</p>
<table cellSpacing=0 cellPadding=0 width="100%" border=0>
    <tbody>
        <tr>
            <td class=code-outline>
            <pre class=displaycode>struct new_ptr_list_t
            {
            new_ptr_list_t*		next;
            const char*			file;
            int					line;
            size_t				size;
            };
            </pre>
            </td>
        </tr>
    </tbody>
</table>
<br>
<p>我最初的实现方法就是每次在使用new分配内存时，调用malloc多分配 sizeof(new_ptr_list_t) 个字节，把分配的内存全部串成一个一个链表（利用next字段），把文件名、行号、对象大小信息分别存入file、line和size字段中，然后返回(malloc返回的指针 + sizeof(new_ptr_list_t))。在delete时，则在链表中搜索，如果找到的话（(char*)链表指针 + sizeof(new_ptr_list_t) == 待释放的指针），则调整链表、释放内存，找不到的话报告删除非法指针并abort。</p>
<p>至于自动检测内存泄漏，我的做法是生成一个静态全局对象（根据C++的对象生命期，在程序初始化时会调用该对象的构造函数，在其退出时会调用该对象的析构函数），在其析构函数中调用检测内存泄漏的函数。用户手工调用内存泄漏检测函数当然也是可以的。</p>
<p>基本实现大体就是如此。</p>
<br>
<table cellSpacing=0 cellPadding=0 width="100%" border=0>
    <tbody>
        <tr>
            <td><img height=1 alt="" src="http://www.ibm.com/i/v14/rules/blue_rule.gif" width="100%"><br><img height=6 alt="" src="http://www.ibm.com/i/c.gif" width=8 border=0></td>
        </tr>
    </tbody>
</table>
<table class=no-print cellSpacing=0 cellPadding=0 align=right>
    <tbody>
        <tr align=right>
            <td><img height=4 alt="" src="http://www.ibm.com/i/c.gif" width="100%"><br>
            <table cellSpacing=0 cellPadding=0 border=0>
                <tbody>
                    <tr>
                        <td vAlign=center><img height=16 alt="" src="http://www.ibm.com/i/v14/icons/u_bold.gif" width=16 border=0><br></td>
                        <td vAlign=top align=right><a class=fbox href="http://www-128.ibm.com/developerworks/cn/linux/l-mleak2/index.html#main" cmImpressionSent="1"><strong><font color=#996699>回页首</font></strong></a></td>
                    </tr>
                </tbody>
            </table>
            </td>
        </tr>
    </tbody>
</table>
<br><br>
<p><a name=4><span class=atitle>可用性改进</span></a></p>
<p>
<p>上述方案最初工作得相当好，直到我开始创建大量的对象为止。由于每次delete时需要在链表中进行搜索，平均搜索次数为(链表长度/2)，程序很快就慢得像乌龟爬。虽说只是用于调试，速度太慢也是不能接受的。因此，我做了一个小更改，把指向链表头部的new_ptr_list改成了一个数组，一个对象指针放在哪一个链表中则由它的哈希值决定。--用户可以更改宏DEBUG_NEW_HASH和DEBUG_NEW_HASHTABLESIZE的定义来调整debug_new的行为。他们的当前值是我测试下来比较满意的定义。</p>
<p>使用中我们发现，在某些特殊情况下（请直接参看debug_new.cpp中关于DEBUG_NEW_FILENAME_LEN部分的注释），文件名指针会失效。因此，目前的debug_new的缺省行为会复制文件名的头20个字符，而不只是存储文件名的指针。另外，请注意原先new_ptr_list_t的长度为16字节，现在是32字节，都能保证在通常情况下内存对齐。</p>
<p>此外，为了允许程序能和 new(std::nothrow) 一起工作，我也重载了operator new(size_t, const std::nothrow_t&amp;) throw()；不然的话，debug_new会认为对应于 new(nothrow) 的delete调用删除的是一个非法指针。由于debug_new不抛出异常（内存不足时程序直接报警退出），所以这一重载的操作只不过是调用 operator new(size_t) 而已。这就不用多说了。</p>
<p>前面已经提到，要得到精确的内存泄漏检测报告，可以在文件开头包含"debug_new.h"。我的惯常做法可以用作参考：</p>
<table cellSpacing=0 cellPadding=0 width="100%" border=0>
    <tbody>
        <tr>
            <td class=code-outline>
            <pre class=displaycode>#ifdef _DEBUG
            #include "debug_new.h"
            #endif
            </pre>
            </td>
        </tr>
    </tbody>
</table>
<br>
<p>包含的位置应当尽可能早，除非跟系统的头文件（典型情况是STL的头文件）发生了冲突。在某些情况下，可能会不希望debug_new重定义new，这时可以在包含debug_new.h之前定义DEBUG_NEW_NO_NEW_REDEFINITION，这样的话，在用户应用程序中应使用debug_new来代替new（顺便提一句，没有定义DEBUG_NEW_NO_NEW_REDEFINITION时也可以使用debug_new代替new）。在源文件中也许就该这样写：</p>
<table cellSpacing=0 cellPadding=0 width="100%" border=0>
    <tbody>
        <tr>
            <td class=code-outline>
            <pre class=displaycode>#ifdef _DEBUG
            #define DEBUG_NEW_NO_NEW_REDEFINITION
            #include "debug_new.h"
            #else
            #define debug_new new
            #endif
            </pre>
            </td>
        </tr>
    </tbody>
</table>
<br>
<p>并在需要追踪内存分配的时候全部使用debug_new（考虑使用全局替换）。</p>
<p>用户可以选择定义DEBUG_NEW_EMULATE_MALLOC，这样debug_new.h会使用debug_new和delete来模拟malloc和free操作，使得用户程序中的malloc和free操作也可以被跟踪。在使用某些编译器的时候（如Digital Mars C++ Compiler 8.29和Borland C++ Compiler 5.5.1），用户必须定义NO_PLACEMENT_DELETE，否则编译无法通过。用户还可以使用两个全局布尔量来调整debug_new的行为：new_verbose_flag，缺省为false，定义为true时能在每次new/delete时向标准错误输出显示跟踪信息；new_autocheck_flag，缺省为true，即在程序退出时自动调用check_leaks检查内存泄漏，改为false的话用户必须手工调用check_leaks来检查内存泄漏。</p>
<p>需要注意的一点是，由于自动调用check_leaks是在debug_new.cpp中的静态对象析构时，因此不能保证用户的全局对象的析构操作发生在check_leaks调用之前。对于Windows上的MSVC，我使用了"#pragma init_seg(lib)"来调整对象分配释放的顺序，但很遗憾，我不知道在其他的一些编译器中（特别是，我没能成功地在GCC中解决这一问题）怎么做到这一点。为了减少误报警，我采取的方式是在自动调用了check_leaks之后设new_verbose_flag为true；这样，就算误报告了内存泄漏，随后的delete操作还是会被打印显示出来。只要泄漏报告和delete报告的内容一致，我们仍可以判断出没有发生内存泄漏。</p>
<p>Debug_new也能检测对同一指针重复调用delete（或delete无效指针）的错误。程序将显示错误的指针值，并强制调用abort退出。</p>
<p>还有一个问题是异常处理。这值得用专门的一节来进行说明。</p>
<br>
<table cellSpacing=0 cellPadding=0 width="100%" border=0>
    <tbody>
        <tr>
            <td><img height=1 alt="" src="http://www.ibm.com/i/v14/rules/blue_rule.gif" width="100%"><br><img height=6 alt="" src="http://www.ibm.com/i/c.gif" width=8 border=0></td>
        </tr>
    </tbody>
</table>
<table class=no-print cellSpacing=0 cellPadding=0 align=right>
    <tbody>
        <tr align=right>
            <td><img height=4 alt="" src="http://www.ibm.com/i/c.gif" width="100%"><br>
            <table cellSpacing=0 cellPadding=0 border=0>
                <tbody>
                    <tr>
                        <td vAlign=center><img height=16 alt="" src="http://www.ibm.com/i/v14/icons/u_bold.gif" width=16 border=0><br></td>
                        <td vAlign=top align=right><a class=fbox href="http://www-128.ibm.com/developerworks/cn/linux/l-mleak2/index.html#main" cmImpressionSent="1"><strong><font color=#996699>回页首</font></strong></a></td>
                    </tr>
                </tbody>
            </table>
            </td>
        </tr>
    </tbody>
</table>
<br><br>
<p><a name=5><span class=atitle>构造函数中的异常</span></a></p>
<p>
<p>我们看一下以下的简单程序示例：</p>
<table cellSpacing=0 cellPadding=0 width="100%" border=0>
    <tbody>
        <tr>
            <td class=code-outline>
            <pre class=displaycode>#include &lt;stdexcept&gt;
            #include &lt;stdio.h&gt;
            void* operator new(size_t size, int line)
            {
            printf("Allocate %u bytes on line %d\\n", size, line);
            return operator new(size);
            }
            class Obj {
            public:
            Obj(int n);
            private:
            int _n;
            };
            Obj::Obj(int n) : _n(n)
            {
            if (n == 0) {
            throw std::runtime_error("0 not allowed");
            }
            }
            int main()
            {
            try {
            Obj* p = new(__LINE__) Obj(0);
            delete p;
            } catch (const std::runtime_error&amp; e) {
            printf("Exception: %s\\n", e.what());
            }
            }
            </pre>
            </td>
        </tr>
    </tbody>
</table>
<br>
<p>看出代码中有什么问题了吗？实际上，如果我们用MSVC编译的话，编译器的警告信息已经告诉我们发生了什么：</p>
<table cellSpacing=0 cellPadding=0 width="100%" border=0>
    <tbody>
        <tr>
            <td class=code-outline>
            <pre class=displaycode>test.cpp(27) : warning C4291: 'void *__cdecl operator new(unsigned int,int)' :
            no matching operator delete found; memory will not be freed if initialization throws an exception
            <!-- code sample is too wide --></pre>
            </td>
        </tr>
    </tbody>
</table>
<br>
<p>好，把debug_new.cpp链接进去。运行结果如下：</p>
<table cellSpacing=0 cellPadding=0 width="100%" border=0>
    <tbody>
        <tr>
            <td class=code-outline>
            <pre class=displaycode>Allocate 4 bytes on line 27 Exception: 0 not allowed Leaked object at 00342BE8 (size 4, &lt;Unknown&gt;:0) 	<!-- code sample is too wide --></pre>
            </td>
        </tr>
    </tbody>
</table>
<br>
<p>啊哦，内存泄漏了不是！</p>
<p>当然，这种情况并非很常见。可是，随着对象越来越复杂，谁能够保证一个对象的子对象的构造函数或者一个对象在构造函数中调用的所有函数都不会抛出异常？并且，解决该问题的方法并不复杂，只是需要编译器对 C++ 标准有较好支持，允许用户定义 placement delete 算符（[C++1998]，5.3.4节；网上可以找到1996年的标准草案，比如下面的网址 <a href="http://www.comnets.rwth-aachen.de/doc/c++std/expr.html#expr.new" cmImpressionSent="1"><u><font color=#5c81a7>http://www.comnets.rwth-aachen.de/doc/c++std/expr.html#expr.new</font></u></a>）。在我测试的编译器中，GCC（2.95.3或更高版本，Linux/Windows）和MSVC（6.0或更高版本）没有问题，而Borland C++ Compiler 5.5.1和Digital Mars C++ Compiler（到v8.38为止的所有版本）则不支持该项特性。在上面的例子中，如果编译器支持的话，我们就需要声明并实现 operator delete(void*, int) 来回收new分配的内存。编译器不支持的话，需要使用宏让编译器忽略相关的声明和实现。如果要让debug_new在Borland C++ Compiler 5.5.1或Digital Mars C++ Compiler下编译的话，用户必须定义宏NO_PLACEMENT_DELETE；当然，用户得自己注意小心构造函数中抛出异常这个问题了。 </p>
<br>
<table cellSpacing=0 cellPadding=0 width="100%" border=0>
    <tbody>
        <tr>
            <td><img height=1 alt="" src="http://www.ibm.com/i/v14/rules/blue_rule.gif" width="100%"><br><img height=6 alt="" src="http://www.ibm.com/i/c.gif" width=8 border=0></td>
        </tr>
    </tbody>
</table>
<table class=no-print cellSpacing=0 cellPadding=0 align=right>
    <tbody>
        <tr align=right>
            <td><img height=4 alt="" src="http://www.ibm.com/i/c.gif" width="100%"><br>
            <table cellSpacing=0 cellPadding=0 border=0>
                <tbody>
                    <tr>
                        <td vAlign=center><img height=16 alt="" src="http://www.ibm.com/i/v14/icons/u_bold.gif" width=16 border=0><br></td>
                        <td vAlign=top align=right><a class=fbox href="http://www-128.ibm.com/developerworks/cn/linux/l-mleak2/index.html#main" cmImpressionSent="1"><strong><font color=#996699>回页首</font></strong></a></td>
                    </tr>
                </tbody>
            </table>
            </td>
        </tr>
    </tbody>
</table>
<br><br>
<p><a name=6><span class=atitle>方案比较</span></a></p>
<p>
<p>IBM developerWorks上刊载了洪琨先生设计实现的一个Linux上的内存泄漏检测方法（[洪琨2003]）。我的方案与其相比，主要区别如下：</p>
<p>优点：
<ul>
    <li>跨平台：只使用标准函数，并且在GCC 2.95.3/3.2（Linux/Windows）、MSVC 6、Digital Mars C++ 8.29、Borland C++ 5.5.1等多个编译器下调试通过。（虽然Linux是我的主要开发平台，但我发现，有时候能在Windows下编译运行代码还是非常方便的。）
    <li>易用性：由于重载了operator new(size_t)--洪琨先生只重载了operator new(size_t, const char*, int)--即使不包含我的头文件也能检测内存泄漏；程序退出时能自动检测内存泄漏；可以检测用户程序（不包括系统/库文件）中malloc/free产生的内存泄漏。
    <li>灵活性：有多个灵活的可配置项，可使用宏定义进行编译时选择。
    <li>可重入性：不使用全局变量，没有嵌套delete问题。
    <li>异常安全性：在编译器支持的情况下，能够处理构造函数中抛出的异常而不发生内存泄漏。 </li>
</ul>
<p>&#160;</p>
<p>缺点：
<ul>
    <li>单线程模型：跨平台的多线程实现较为麻烦，根据项目的实际需要，也为了代码清晰简单起见，我的方案不是线程安全的；换句话说，如果多个线程中同时进行new或delete操作的话，后果未定义。
    <li>未实现运行中内存泄漏检测报告机制：没有遇到这个需求J；不过，如果要手工调用check_leaks函数实现的话也不困难，只是跨平台性就有点问题了。
    <li>不能检测带 [] 算符和不带 [] 算符混用的不匹配：主要也是需求问题（如果要修改实现的话并不困难）。
    <li>不能在错误的delete调用时显示文件名和行号：应该不是大问题；由于我重载了operator new(size_t)，可以保证delete出错时程序必然有问题，因而我不只是显示警告信息，而且会强制程序abort，可以通过跟踪程序、检查abort时程序的调用栈知道问题出在哪儿。 </li>
</ul>
<p>&#160;</p>
<p>另外，现在已存在不少商业和Open Source的内存泄漏检测器，本文不打算一一再做比较。Debug_new与它们相比，功能上总的来说仍较弱，但是，其良好的易用性和跨平台性、低廉的附加开销还是具有很大优势的。</p>
<br>
<table cellSpacing=0 cellPadding=0 width="100%" border=0>
    <tbody>
        <tr>
            <td><img height=1 alt="" src="http://www.ibm.com/i/v14/rules/blue_rule.gif" width="100%"><br><img height=6 alt="" src="http://www.ibm.com/i/c.gif" width=8 border=0></td>
        </tr>
    </tbody>
</table>
<table class=no-print cellSpacing=0 cellPadding=0 align=right>
    <tbody>
        <tr align=right>
            <td><img height=4 alt="" src="http://www.ibm.com/i/c.gif" width="100%"><br>
            <table cellSpacing=0 cellPadding=0 border=0>
                <tbody>
                    <tr>
                        <td vAlign=center><img height=16 alt="" src="http://www.ibm.com/i/v14/icons/u_bold.gif" width=16 border=0><br></td>
                        <td vAlign=top align=right><a class=fbox href="http://www-128.ibm.com/developerworks/cn/linux/l-mleak2/index.html#main" cmImpressionSent="1"><strong><font color=#996699>回页首</font></strong></a></td>
                    </tr>
                </tbody>
            </table>
            </td>
        </tr>
    </tbody>
</table>
<br><br>
<p><a name=7><span class=atitle>总结和讨论</span></a></p>
<p>
<p>以上段落基本上已经说明了debug_new的主要特点。下面做一个小小的总结。</p>
<p>重载的算符：
<ul>
    <li>operator new(size_t, const char*, int)
    <li>operator new[](size_t, const char*, int)
    <li>operator new(size_t)
    <li>operator new[](size_t)
    <li>operator new(size_t, const std::nothrow_t&amp;)
    <li>operator new[](size_t, const std::nothrow_t&amp;)
    <li>operator delete(void*)
    <li>operator delete[](void*)
    <li>operator delete(void*, const char*, int)
    <li>operator delete[](void*, const char*, int)
    <li>operator delete(void*, const std::nothrow_t&amp;)
    <li>operator delete[](void*, const std::nothrow_t&amp;) </li>
</ul>
<p>&#160;</p>
<p>提供的函数：
<ul>
    <li>check_leaks() <br>检查是否发生内存泄漏 </li>
</ul>
<p>&#160;</p>
<p>提供的全局变量
<ul>
    <li>new_verbose_flag <br>是否在new和delete时"罗嗦"地显示信息
    <li>new_autocheck_flag <br>是否在程序退出是自动检测一次内存泄漏 </li>
</ul>
<p>&#160;</p>
<p>可重定义的宏：
<ul>
    <li>NO_PLACEMENT_DELETE <br>假设编译器不支持placement delete（全局有效）
    <li>DEBUG_NEW_NO_NEW_REDEFINITION <br>不重定义new，假设用户会自己使用debug_new（包含debug_new.h时有效）
    <li>DEBUG_NEW_EMULATE_MALLOC <br>重定义malloc/free，使用new/delete进行模拟（包含debug_new.h时有效）
    <li>DEBUG_NEW_HASH <br>改变内存块链表哈希值的算法（编译debug_new.cpp时有效）
    <li>DEBUG_NEW_HASHTABLE_SIZE <br>改变内存块链表哈希桶的大小（编译debug_new.cpp时有效）
    <li>DEBUG_NEW_FILENAME_LEN <br>如果在分配内存时复制文件名的话，保留的文件名长度；为0时则自动定义DEBUG_NEW_NO_FILENAME_COPY（编译debug_new.cpp时有效；参见文件中的注释）
    <li>DEBUG_NEW_NO_FILENAME_COPY <br>分配内存时不进行文件名复制，而只是保存其指针；效率较高（编译debug_new.cpp时有效；参见文件中的注释） </li>
</ul>
<p>&#160;</p>
<p>我本人认为，debug_new目前的一个主要缺陷是不支持多线程。对于某一特定平台，要加入多线程支持并不困难，难就难在通用上（当然，条件编译是一个办法，虽然不够优雅）。等到C++标准中包含线程模型时，这个问题也许能比较完美地解决吧。另一个办法是使用像boost这样的程序库中的线程封装类，不过，这又会增加对其它库的依赖性--毕竟boost并不是C++标准的一部分。如果项目本身并不用boost，单为了这一个目的使用另外一个程序库似乎并不值得。因此，我自己暂时就不做这进一步的改进了。</p>
<p>另外一个可能的修改是保留标准operator new的异常行为，使其在内存不足的情况下抛出异常（普通情况）或是返回NULL（nothrow情况），而不是像现在一样终止程序运行（参见debug_new.cpp的源代码）。这一做法的难度主要在于后者：我没想出什么方法，可以保留 new(nothrow) 的语法，同时能够报告文件名和行号，并且还能够使用普通的new。不过，如果不使用标准语法，一律使用debug_new和debug_new_nothrow的话，那还是非常容易实现的。</p>
<p>如果大家有改进意见或其它想法的话，欢迎来信讨论。</p>
<p>debug_new 的源代码目前可以在 <a href="http://www-128.ibm.com/developerworks/cn/linux/l-mleak2/dbg_new.zip" cmImpressionSent="1"><u><font color=#5c81a7>dbg_new.zip</font></u></a>处下载。 </p>
<p>在这篇文章的写完之后，我终于还是实现了一个线程安全的版本。该版本使用了一个轻量级的跨平台互斥体类fast_mutex（目前支持Win32和POSIX线程，在使用GCC（Linux/MinGW）、MSVC时能通过命令行参数自动检测线程类型）。有兴趣的话可在 <a href="http://mywebpage.netscape.com/yongweiwu/dbg_new.tgz" cmImpressionSent="1"><u><font color=#5c81a7>http://mywebpage.netscape.com/yongweiwu/dbg_new.tgz</font></u></a>下载。 </p>
<br><br>
<p><a name=resources><span class=atitle>参考资料 </span></a></p>
<p>[C++1998] ISO/IEC 14882. Programming Languages-C++, 1st Edition. International Standardization Organization, International Electrotechnical Commission, American National Standards Institute, and Information Technology Industry Council, 1998</p>
<p>[Stroustrup1997] Bjarne Stroustrup. The C++ Programming Language, 3rd Edition. Addison-Wesley, 1997</p>
<p>[洪琨2003] 洪琨。 <a href="http://www-128.ibm.com/developerworks/cn/linux/l-mleak/index.html" cmImpressionSent="1"><u><font color=#5c81a7>《如何在 linux 下检测内存泄漏》</font></u></a>，IBM developerWorks 中国网站。 </p>
<img src ="http://www.cppblog.com/SpringSnow/aggbug/65742.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/SpringSnow/" target="_blank">Sandy</a> 2008-11-02 10:56 <a href="http://www.cppblog.com/SpringSnow/articles/65742.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>转：浅谈内存泄漏（二）</title><link>http://www.cppblog.com/SpringSnow/articles/65740.html</link><dc:creator>Sandy</dc:creator><author>Sandy</author><pubDate>Sun, 02 Nov 2008 02:54:00 GMT</pubDate><guid>http://www.cppblog.com/SpringSnow/articles/65740.html</guid><wfw:comment>http://www.cppblog.com/SpringSnow/comments/65740.html</wfw:comment><comments>http://www.cppblog.com/SpringSnow/articles/65740.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/SpringSnow/comments/commentRss/65740.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/SpringSnow/services/trackbacks/65740.html</trackback:ping><description><![CDATA[<a href="http://www.vczx.com/article/show.php?id=68">http://www.vczx.com/article/show.php?id=68</a><br><br>检测内存泄漏： <br>检测内存泄漏的关键是要能截获住对分配内存和释放内存的函数的调用。截获住这两个函数，我们就能跟踪每一块内存的生命周期，比如，每当成功的分配一块内存后，就把它的指针加入一个全局的list中；每当释放一块内存，再把它的指针从list中删除。这样，当程序结束的时候，list中剩余的指针就是指向那些没有被释放的内存。这里只是简单的描述了检测内存泄漏的基本原理，详细的算法可以参见Steve Maguire的&lt;&lt;Writing Solid Code&gt;&gt;。 <br>如果要检测堆内存的泄漏，那么需要截获住malloc/realloc/free和new/delete就可以了（其实new/delete最终也是用malloc/free的，所以只要截获前面一组即可）。对于其他的泄漏，可以采用类似的方法，截获住相应的分配和释放函数。比如，要检测BSTR的泄漏，就需要截获SysAllocString/SysFreeString；要检测HMENU的泄漏，就需要截获CreateMenu/ DestroyMenu。（有的资源的分配函数有多个，释放函数只有一个，比如，SysAllocStringLen也可以用来分配BSTR，这时就需要截获多个分配函数） <br>在Windows平台下，检测内存泄漏的工具常用的一般有三种，MS C-Runtime Library内建的检测功能；外挂式的检测工具，诸如，Purify，BoundsChecker等；利用Windows NT自带的Performance Monitor。这三种工具各有优缺点，MS C-Runtime Library虽然功能上较之外挂式的工具要弱，但是它是免费的；Performance Monitor虽然无法标示出发生问题的代码，但是它能检测出隐式的内存泄漏的存在，这是其他两类工具无能为力的地方。 <br>以下我们详细讨论这三种检测工具： <br>VC下内存泄漏的检测方法 <br>用MFC开发的应用程序，在DEBUG版模式下编译后，都会自动加入内存泄漏的检测代码。在程序结束后，如果发生了内存泄漏，在Debug窗口中会显示出所有发生泄漏的内存块的信息，以下两行显示了一块被泄漏的内存块的信息： <br>E:\TestMemLeak\TestDlg.cpp(70)&nbsp;&nbsp;&nbsp;&nbsp; : {59} normal block at 0x00881710, 200 bytes long. <br>Data: &lt;abcdefghijklmnop&gt;&nbsp;&nbsp;&nbsp;&nbsp; 61 62 63 64 65 66 67 68 69 6A 6B 6C 6D 6E 6F 70 <br>第一行显示该内存块由TestDlg.cpp文件，第70行代码分配，地址在0x00881710，大小为200字节，{59}是指调用内存分配函数的Request Order，关于它的详细信息可以参见MSDN中_CrtSetBreakAlloc()的帮助。第二行显示该内存块前16个字节的内容，尖括号内是以ASCII方式显示，接着的是以16进制方式显示。 <br>一般大家都误以为这些内存泄漏的检测功能是由MFC提供的，其实不然。MFC只是封装和利用了MS C-Runtime Library的Debug Function。非MFC程序也可以利用MS C-Runtime Library的Debug Function加入内存泄漏的检测功能。MS C-Runtime Library在实现malloc/free，strdup等函数时已经内建了内存泄漏的检测功能。 <br>注意观察一下由MFC Application Wizard生成的项目，在每一个cpp文件的头部都有这样一段宏定义： <br>#ifdef&nbsp;&nbsp;&nbsp;&nbsp; _DEBUG <br>#define&nbsp;&nbsp;&nbsp;&nbsp; new DEBUG_NEW <br>#undef&nbsp;&nbsp;&nbsp;&nbsp; THIS_FILE <br>static&nbsp;&nbsp;&nbsp;&nbsp; char THIS_FILE[] = __FILE__; <br>#endif <br>有了这样的定义，在编译DEBUG版时，出现在这个cpp文件中的所有new都被替换成DEBUG_NEW了。那么DEBUG_NEW是什么呢？DEBUG_NEW也是一个宏，以下摘自afx.h，1632行 <br>#define&nbsp;&nbsp;&nbsp;&nbsp; DEBUG_NEW new(THIS_FILE, __LINE__) <br>所以如果有这样一行代码： <br>char*&nbsp;&nbsp;&nbsp;&nbsp; p = new char[200]; <br>经过宏替换就变成了： <br>char*&nbsp;&nbsp;&nbsp;&nbsp; p = new( THIS_FILE, __LINE__)char[200]; <br>根据C++的标准，对于以上的new的使用方法，编译器会去找这样定义的operator new： <br>void*&nbsp;&nbsp;&nbsp;&nbsp; operator new(size_t, LPCSTR, int) <br>我们在afxmem.cpp 63行找到了一个这样的operator new 的实现 <br>void*&nbsp;&nbsp;&nbsp;&nbsp; AFX_CDECL operator new(size_t nSize, LPCSTR lpszFileName, int nLine) <br>{ <br>&nbsp;&nbsp;&nbsp; return&nbsp;&nbsp;&nbsp;&nbsp; ::operator new(nSize, _NORMAL_BLOCK, lpszFileName, nLine); <br>} <br>void*&nbsp;&nbsp;&nbsp;&nbsp; __cdecl operator new(size_t nSize, int nType, LPCSTR lpszFileName, int&nbsp;&nbsp;&nbsp;&nbsp; nLine) <br>{ <br>&nbsp;&nbsp;&nbsp; &#8230; <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;pResult = _malloc_dbg(nSize, nType,&nbsp;&nbsp;&nbsp;&nbsp; lpszFileName, nLine); <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if (pResult != NULL) <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return pResult; <br>&nbsp;&nbsp;&nbsp; &#8230; <br>} <br>第二个operator new函数比较长，为了简单期间，我只摘录了部分。很显然最后的内存分配还是通过_malloc_dbg函数实现的，这个函数属于MS C-Runtime Library 的Debug Function。这个函数不但要求传入内存的大小，另外还有文件名和行号两个参数。文件名和行号就是用来记录此次分配是由哪一段代码造成的。如果这块内存在程序结束之前没有被释放，那么这些信息就会输出到Debug窗口里。 <br>这里顺便提一下THIS_FILE，__FILE和__LINE__。__FILE__和__LINE__都是编译器定义的宏。当碰到__FILE__时，编译器会把__FILE__替换成一个字符串，这个字符串就是当前在编译的文件的路径名。当碰到__LINE__时，编译器会把__LINE__替换成一个数字，这个数字就是当前这行代码的行号。在DEBUG_NEW的定义中没有直接使用__FILE__，而是用了THIS_FILE，其目的是为了减小目标文件的大小。假设在某个cpp文件中有100处使用了new，如果直接使用__FILE__，那编译器会产生100个常量字符串，这100个字符串都是这个cpp文件的路径名，显然十分冗余。如果使用THIS_FILE，编译器只会产生一个常量字符串，那100处new的调用使用的都是指向常量字符串的指针。 <br>再次观察一下由MFC Application Wizard生成的项目，我们会发现在cpp文件中只对new做了映射，如果你在程序中直接使用malloc函数分配内存，调用malloc的文件名和行号是不会被记录下来的。如果这块内存发生了泄漏，MS C-Runtime Library仍然能检测到，但是当输出这块内存块的信息，不会包含分配它的的文件名和行号。 <br>要在非MFC程序中打开内存泄漏的检测功能非常容易，你只要在程序的入口处加入以下几行代码： <br>int&nbsp;&nbsp;&nbsp;&nbsp; tmpFlag = _CrtSetDbgFlag( _CRTDBG_REPORT_FLAG ); <br>tmpFlag&nbsp;&nbsp;&nbsp;&nbsp; |= _CRTDBG_LEAK_CHECK_DF; <br>_CrtSetDbgFlag(&nbsp;&nbsp;&nbsp;&nbsp; tmpFlag ); <br>这样，在程序结束的时候，也就是winmain，main或dllmain函数返回之后，如果还有内存块没有释放，它们的信息会被打印到Debug窗口里。 <br>如果你试着创建了一个非MFC应用程序，而且在程序的入口处加入了以上代码，并且故意在程序中不释放某些内存块，你会在Debug窗口里看到以下的信息： <br>{47}&nbsp;&nbsp;&nbsp;&nbsp; normal block at 0x00C91C90, 200 bytes long. <br>Data: &lt;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&gt; 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F <br>内存泄漏的确检测到了，但是和上面MFC程序的例子相比，缺少了文件名和行号。对于一个比较大的程序，没有这些信息，解决问题将变得十分困难。 <br>为了能够知道泄漏的内存块是在哪里分配的，你需要实现类似MFC的映射功能，把new，maolloc等函数映射到_malloc_dbg函数上。这里我不再赘述，你可以参考MFC的源代码。 <br>由于Debug Function实现在MS C-RuntimeLibrary中，所以它只能检测到堆内存的泄漏，而且只限于malloc，realloc或strdup等分配的内存，而那些系统资源，比如HANDLE，GDI Object，或是不通过C-Runtime Library分配的内存，比如VARIANT，BSTR的泄漏，它是无法检测到的，这是这种检测法的一个重大的局限性。另外，为了能记录内存块是在哪里分配的，源代码必须相应的配合，这在调试一些老的程序非常麻烦，毕竟修改源代码不是一件省心的事，这是这种检测法的另一个局限性。 <br>对于开发一个大型的程序，MS C-Runtime Library提供的检测功能是远远不够的。接下来我们就看看外挂式的检测工具。我用的比较多的是BoundsChecker，一则因为它的功能比较全面，更重要的是它的稳定性。这类工具如果不稳定，反而会忙里添乱。到底是出自鼎鼎大名的NuMega，我用下来基本上没有什么大问题。 <br>
<img src ="http://www.cppblog.com/SpringSnow/aggbug/65740.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/SpringSnow/" target="_blank">Sandy</a> 2008-11-02 10:54 <a href="http://www.cppblog.com/SpringSnow/articles/65740.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>转：内存泄露检测 </title><link>http://www.cppblog.com/SpringSnow/articles/65739.html</link><dc:creator>Sandy</dc:creator><author>Sandy</author><pubDate>Sun, 02 Nov 2008 02:53:00 GMT</pubDate><guid>http://www.cppblog.com/SpringSnow/articles/65739.html</guid><wfw:comment>http://www.cppblog.com/SpringSnow/comments/65739.html</wfw:comment><comments>http://www.cppblog.com/SpringSnow/articles/65739.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/SpringSnow/comments/commentRss/65739.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/SpringSnow/services/trackbacks/65739.html</trackback:ping><description><![CDATA[<a href="http://www.cppblog.com/Ipedo/archive/2005/10/27/867.aspx"><br>http://www.cppblog.com/Ipedo/archive/2005/10/27/867.aspx</a><br><br>
<div class=postTitle>&nbsp;c＋＋中检测内存泄漏可以引入系统定义的宏来查看，内存在哪个位置泄漏<br><br>文件开始处加入下列定义<br>#define _CRTDBG_MAP_ALLOC<br>#include &lt;stdlib.h&gt;<br>#include &lt;crtdbg.h&gt;<br><br>程序退出时加入以下函数：<br><br>_CrtDumpMemoryLeaks();<br><br>如果有泄漏会显示<br>e:\myproject\mltithrd.14\mltithrd.cpp(95) : {68} client block at 0x00372550, subtype c0, 144 bytes long.<br>a CMultiDocTemplate object at $00372550, 144 bytes long </div>
<img src ="http://www.cppblog.com/SpringSnow/aggbug/65739.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/SpringSnow/" target="_blank">Sandy</a> 2008-11-02 10:53 <a href="http://www.cppblog.com/SpringSnow/articles/65739.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>转：检测内存泄露的工具：debugnew</title><link>http://www.cppblog.com/SpringSnow/articles/65738.html</link><dc:creator>Sandy</dc:creator><author>Sandy</author><pubDate>Sun, 02 Nov 2008 02:52:00 GMT</pubDate><guid>http://www.cppblog.com/SpringSnow/articles/65738.html</guid><wfw:comment>http://www.cppblog.com/SpringSnow/comments/65738.html</wfw:comment><comments>http://www.cppblog.com/SpringSnow/articles/65738.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/SpringSnow/comments/commentRss/65738.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/SpringSnow/services/trackbacks/65738.html</trackback:ping><description><![CDATA[<span id=ArticleContent1_ArticleContent1_lblContent>&nbsp;
<p><font size=5><strong>检测内存泄露的工具：debugnew<br><a href="http://dev.csdn.net/article/58/58407.shtm">http://dev.csdn.net/article/58/58407.shtm</a><br><br></strong></font>网上有一个流传甚广的检测内存泄露的工具：debugnew(debugnew.h/debugnew.cpp)<br>用法很简单，把debugnew.cpp放在项目里一起编译，需要检测的文件把debugnew.h嵌在文件的最前面。<br><br>为方便使用，对源代码做了一些小的改动。<br><br>下面是一些简单的说明：</p>
<p><strong><font size=4>1、new 的重载</font><br></strong>void* operator new (size_t size, const char* file, int line)；　　　　　　　　⑴<br>void* operator new [] (size_t size, const char*&nbsp; file, int line)；　　　　　　⑵</p>
<p>在需要检测的文件里，重定义new<br>#define new new(__FILE__, __LINE__)</p>
<p>造成的结果：<br>ClassName *p = new ClassName; =&gt; ClassName *p = new(__FILE__, __LINE__) ClassName; <br>// 实际会调用void* operator new (size_t size, const char* file, int line)；</p>
<p>ClassName **pp = new classname[count]; =&gt; ClassName **pp = new(__FILE__, __LINE__) ClassName[count];<br>// 实际会调用void* operator new [] (size_t size, const char*&nbsp; file, int line)；</p>
<p>这其实是利用了placement new的语法，通过一个简单的宏，就可以把普通的new操作对应到相应的重载( ⑴,⑵ )上去。</p>
<p><font size=4><strong>2、delete 的重载</strong><br></font>void operator delete (void* p, const char* file, int line);&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ⑶<br>void operator delete [] (void* p, const char* file, int line);&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ⑷<br>void operator delete (void* p);&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;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ⑸<br>void operator delete [] (void* p);&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;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ⑹</p>
<p>因为没有类似于placement new的语法，所以就不能用一个宏来替换替换delete了。要调用带有更多信息的delete操作符，只能修改源代码了。<br>delete p; =&gt; delete ( p, __FILE__, __LINE__ );<br>delete []pp; =&gt; delete [] ( pp, __FILE__, __LINE__ );<br>但这个工作很烦琐，如果并不需要多余的信息的话，简单地重载delete( ⑸,⑹ )就可以了。</p>
<p><strong><font size=4>3、检测和统计</font><br></strong>程序开始时，在debugnew.cpp中会创建一个DebugNewTracer对象<br>在重载的new操作符( ⑴,⑵ )中，每一次内存分配都会被记录，而在delete( ⑶,⑷,⑸,⑹ )中则会删除相应的记录。<br>当程序结束，DebugNewTracer对象被销毁，它的析构函数会dump剩余的记录，这就是泄露的内存了。</p>
<p>在原有代码的基础上，增加了记录size的功能，这样可以在每次new和delete时，看到实际占用的内存。所有信息可以dump出来，也可以写入log。<br><br><br><font size=4><strong>5、源代码<br></strong></font><br>********************************************************<br><strong>debugnew.h:</strong></p>
<p>/*<br>&nbsp;filename: debugnew.h<br>&nbsp;<br>&nbsp;This code is based on code retrieved from a web site. The<br>&nbsp;author was not identified, so thanks go to anonymous.</p>
<p>&nbsp;This is used to substitute a version of the new operator that<br>&nbsp;can be used for debugging memory leaks. To use it:<br>&nbsp;<br>&nbsp;- In any (all?) code files #include debugnew.h. Make sure all<br>&nbsp;&nbsp;system files (i.e. those in &lt;&gt;'s) are #included before<br>&nbsp;&nbsp;debugnew.h, and that any header files for your own code<br>&nbsp;&nbsp;are #included after debugnew.h. The reason is that some<br>&nbsp;&nbsp;system files refer to ::new, and this will not compile<br>&nbsp;&nbsp;if debugnew is in effect. You may still have problems<br>&nbsp;&nbsp;if any of your own code refers to ::new, or if any<br>&nbsp;&nbsp;of your own files #include system files that use ::new<br>&nbsp;&nbsp;and which have not already been #included before<br>&nbsp;&nbsp;debugnew.h.<br>&nbsp;- Add debugnew.cpp to the CodeWarrior project or compile<br>&nbsp;&nbsp;it into your Linux executable. If debugnew.cpp is in the<br>&nbsp;&nbsp;project, then debugnew.h must be #included in at least<br>&nbsp;&nbsp;one source file<br>*/</p>
<p>#ifndef __DEBUGNEW_H__<br>#define __DEBUGNEW_H__</p>
<p>#include &lt;map&gt;</p>
<p>#define LOG_FILE</p>
<p>#if defined(LOG_FILE)<br>#define LOG_FILE_NAME "./debugnew.log"<br>#endif</p>
<p>void* operator new (std::size_t size, const char* file, int line);<br>void operator delete (void* p, const char* name, int line);<br>void* operator new [] (std::size_t size, const char* file, int line);<br>void operator delete [] (void* p, const char* name, int line);</p>
<p>class DebugNewTracer&nbsp; {<br>private:<br>&nbsp;<br>&nbsp;class Entry&nbsp; {<br>&nbsp;public:<br>&nbsp;&nbsp;Entry (char const* file, int line) : _file (file), _line (line)&nbsp;{}<br>&nbsp;&nbsp;Entry (char const* file, int line, int size) : _file (file), _line (line), _size (size) {}<br>&nbsp;&nbsp;Entry () : _file (0), _line (0), _size (0) {}<br>&nbsp;&nbsp;const char* File () const { return _file; }<br>&nbsp;&nbsp;int Line () const { return _line; }<br>&nbsp;&nbsp;size_t Size () const { return _size; }<br>&nbsp;private:<br>&nbsp;&nbsp;char const* _file;<br>&nbsp;&nbsp;int _line;<br>&nbsp;&nbsp;size_t _size;<br>&nbsp;};</p>
<p>&nbsp;class Lock {<br>&nbsp;public:<br>&nbsp;&nbsp;Lock (DebugNewTracer &amp; DebugNewTracer) : _DebugNewTracer (DebugNewTracer) {<br>&nbsp;&nbsp;&nbsp;_DebugNewTracer.lock ();<br>&nbsp;&nbsp;}<br>&nbsp;&nbsp;~Lock () {<br>&nbsp;&nbsp;&nbsp;_DebugNewTracer.unlock ();<br>&nbsp;&nbsp;}<br>&nbsp;private:<br>&nbsp;&nbsp;DebugNewTracer &amp; _DebugNewTracer;<br>&nbsp;};<br>&nbsp;typedef std::map&lt;void*, Entry&gt;::iterator iterator;<br>&nbsp;friend class Lock;<br>&nbsp;public:<br>&nbsp;DebugNewTracer ();<br>&nbsp;~DebugNewTracer ();<br>&nbsp;void Add (void* p, const char* file, int line);<br>&nbsp;void Add (void* p, const char* file, int line, size_t size);<br>&nbsp;void Remove (void* p);<br>&nbsp;void Dump ();</p>
<p>&nbsp;static bool Ready;</p>
<p>&nbsp;private:<br>&nbsp;void lock () { _lockCount++; }<br>&nbsp;void unlock () { _lockCount--; }</p>
<p>&nbsp;private:</p>
<p>&nbsp;std::map&lt;void*, Entry&gt; _map;<br>&nbsp;int _lockCount;<br>&nbsp;size_t _totalsize;<br>#if defined(LOG_FILE)<br>&nbsp;FILE* _logfp;<br>#endif<br>&nbsp;};</p>
<p>&nbsp;// The file that implements class DebugNewTracer<br>&nbsp;// does NOT want the word "new" expanded.<br>&nbsp;// The object DebugNewTrace is defined in that<br>&nbsp;// implementation file but only declared in any other file.<br>#ifdef DEBUGNEW_CPP<br>DebugNewTracer DebugNewTrace;<br>#else<br>#define new new(__FILE__, __LINE__)<br>extern DebugNewTracer DebugNewTrace;<br>#endif</p>
<p>#endif//#ifndef __DEBUGNEW_H__</p>
<p><br>********************************************************<br><strong>debugnew.cpp:</strong></p>
<p>/*<br>&nbsp;filename: debugnew.cpp</p>
<p>&nbsp;This is used to substitute a version of the new operator that<br>&nbsp;can be used for debugging memory leaks. In any (all?) code<br>&nbsp;files #include debugnew.h. Add debugnew.cpp to the project.<br>*/</p>
<p>#include &lt;iostream&gt;<br>#include &lt;map&gt;</p>
<p>using namespace std;</p>
<p>&nbsp;// This disables macro expansion of "new".<br>&nbsp;// This statement should only appear in this file.<br>#define DEBUGNEW_CPP</p>
<p>#include "debugnew.h"</p>
<p>DebugNewTracer::DebugNewTracer () : _lockCount (0) <br>{<br>&nbsp;&nbsp;// Once this object is constructed all calls to<br>&nbsp;&nbsp;// new should be traced.</p>
<p>&nbsp;Ready = true;<br>&nbsp;_totalsize = 0;<br>#if defined(LOG_FILE)<br>&nbsp;if( (_logfp=fopen(LOG_FILE_NAME,"wt")) == NULL )<br>&nbsp;{<br>&nbsp;&nbsp;printf(" Error! file: debugnew.log can not <a href="&#109;&#97;&#105;&#108;&#116;&#111;&#58;&#111;&#112;&#101;&#110;&#64;&#33;&#92;&#110;"><u><font color=#0000ff>open@!\n</font></u></a>");<br>&nbsp;&nbsp;return;<br>&nbsp;}&nbsp;<br>&nbsp;fprintf(_logfp,"new, delete list:\n");<br>&nbsp;fflush(_logfp);<br>#endif<br>&nbsp;<br>}<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <br>void DebugNewTracer::Add (void* p, const char* file, int line)<br>{<br>&nbsp;&nbsp;// Tracing must not be done a second time<br>&nbsp;&nbsp;// while it is already<br>&nbsp;&nbsp;// in the middle of executing<br>&nbsp;if (_lockCount &gt; 0)<br>&nbsp;&nbsp;return;</p>
<p>&nbsp;&nbsp;&nbsp;// Tracing must be disabled while the map<br>&nbsp;&nbsp;&nbsp;// is in use in case it calls new.<br>&nbsp;DebugNewTracer::Lock lock (*this);<br>&nbsp;_map [p] = Entry (file, line);<br>}</p>
<p>void DebugNewTracer::Add (void* p, const char* file, int line, size_t size)<br>{<br>&nbsp;// Tracing must not be done a second time<br>&nbsp;// while it is already<br>&nbsp;// in the middle of executing<br>&nbsp;if (_lockCount &gt; 0)<br>&nbsp;&nbsp;return;</p>
<p>&nbsp;&nbsp;// Tracing must be disabled while the map<br>&nbsp;&nbsp;// is in use in case it calls new.<br>&nbsp;DebugNewTracer::Lock lock (*this);<br>#if 1<br>&nbsp;//&#180;&#211;&#200;&#171;&#194;&#183;&#190;&#182;&#214;&#208;&#204;&#225;&#200;&#161;&#206;&#196;&#188;&#254;&#195;&#251;<br>&nbsp;//linux&#207;&#194;&#181;&#196;gcc,__FILE__&#189;&#246;&#189;&#246;&#176;&#252;&#192;&#168;&#206;&#196;&#188;&#254;&#195;&#251;&#163;&#172;windows&#207;&#194;&#181;&#196;vc,__FILE__&#176;&#252;&#192;&#168;&#200;&#171;&#194;&#183;&#190;&#182;,&#203;&#249;&#210;&#212;&#211;&#208;&#213;&#226;&#209;&#249;&#181;&#196;&#180;&#166;&#192;&#237;<br>&nbsp;file = strrchr(file,'\\')== NULL?file:strrchr(file,'\\')+1;<br>#endif<br>&nbsp;_map [p] = Entry (file, line, size);<br>&nbsp;_totalsize += size;<br>#if defined(LOG_FILE)<br>&nbsp;fprintf(_logfp,"*N p = 0x%08x, size = %6d,&nbsp; %-22s, Line: %4d.&nbsp; totalsize =%8d\n", p, size, file, line, _totalsize);<br>&nbsp;fflush(_logfp);<br>#endif<br>}</p>
<p><br>void DebugNewTracer::Remove (void* p)<br>{<br>&nbsp;// Tracing must not be done a second time<br>&nbsp;// while it is already<br>&nbsp;// in the middle of executing<br>&nbsp;if (_lockCount &gt; 0)<br>&nbsp;&nbsp;return;</p>
<p>&nbsp;&nbsp;// Tracing must be disabled while the map<br>&nbsp;&nbsp;// is in use in case it calls new.<br>&nbsp;DebugNewTracer::Lock lock (*this);</p>
<p>&nbsp;iterator it = _map.find (p);</p>
<p>&nbsp;if (it != _map.end ())<br>&nbsp;{<br>&nbsp;&nbsp;size_t size = (*it).second.Size();<br>&nbsp;&nbsp;_totalsize -= size;<br>#if defined(LOG_FILE)<br>&nbsp;&nbsp;fprintf(_logfp,"#D p = 0x%08x, size = %6u.%39stotalsize =%8d\n", p, size, "-----------------------------------&nbsp; ", _totalsize );<br>&nbsp;&nbsp;fflush(_logfp);<br>#endif<br>&nbsp;&nbsp;_map.erase (it);<br>&nbsp;}<br>&nbsp;else<br>&nbsp;{<br>#if defined(LOG_FILE)<br>&nbsp;&nbsp;fprintf(_logfp,"#D p = 0x%08x. error point!!!\n", p );<br>&nbsp;&nbsp;fflush(_logfp);<br>#endif<br>&nbsp;}<br>}</p>
<p>DebugNewTracer::~DebugNewTracer ()<br>{<br>&nbsp;// Trace must not be called if Dump indirectly<br>&nbsp;// invokes new, so it must be disabled before<br>&nbsp;// Dump is called. After this destructor executes<br>&nbsp;// any other global objects that get destructed<br>&nbsp;// should not do any tracing.<br>&nbsp;Ready = false;<br>&nbsp;Dump ();<br>#if defined(LOG_FILE)<br>&nbsp;fclose(_logfp);<br>#endif<br>}</p>
<p>// If some global object is destructed after DebugNewTracer<br>// and if that object calls new, it should not trace that<br>// call to new.<br>void DebugNewTracer::Dump ()<br>{<br>&nbsp;if (_map.size () != 0)<br>&nbsp;{<br>&nbsp;&nbsp;std::cout &lt;&lt; _map.size () &lt;&lt; " memory leaks detected\n";<br>#if defined(LOG_FILE)<br>&nbsp;&nbsp;fprintf(_logfp, "\n\n***%d memory leaks detected\n", _map.size ());<br>&nbsp;&nbsp;fflush(_logfp);<br>#endif<br>&nbsp;&nbsp;for (iterator it = _map.begin (); it != _map.end (); ++it)<br>&nbsp;&nbsp;{<br>&nbsp;&nbsp;&nbsp;char const * file = it-&gt;second.File ();<br>&nbsp;&nbsp;&nbsp;int line = it-&gt;second.Line ();<br>&nbsp;&nbsp;&nbsp;int size = it-&gt;second.Size ();<br>&nbsp;&nbsp;&nbsp;std::cout &lt;&lt; file &lt;&lt; ", "&nbsp; &lt;&lt; line &lt;&lt; std::endl;<br>#if defined(LOG_FILE)<br>&nbsp;&nbsp;&nbsp;fprintf(_logfp,"%s, %d, size=%d\n", file, line, size);<br>&nbsp;&nbsp;&nbsp;fflush(_logfp);<br>#endif<br>&nbsp;&nbsp;}<br>&nbsp;}<br>&nbsp;else<br>&nbsp;{<br>&nbsp;&nbsp;std::cout &lt;&lt; "no leaks detected\n";<br>#if defined(LOG_FILE)<br>&nbsp;&nbsp;fprintf(_logfp,"no leaks detected\n");<br>&nbsp;&nbsp;fflush(_logfp);<br>#endif<br>&nbsp;}</p>
<p>}</p>
<p>// If some global object is constructed before DebugNewTracer<br>// and if that object calls new, it should not trace that<br>// call to new.<br>bool DebugNewTracer::Ready = false;</p>
<p>void* operator new (size_t size, const char* file, int line)<br>{<br>&nbsp;void * p = malloc (size);<br>&nbsp;if (DebugNewTracer::Ready)<br>&nbsp;&nbsp;DebugNewTrace.Add (p, file, line, size);<br>&nbsp;return p;<br>}</p>
<p>void operator delete (void* p, const char* file, int line)<br>{<br>&nbsp;file = 0;&nbsp;// avoid a warning about argument not used in function<br>&nbsp;line = 0;&nbsp;// avoid a warning about argument not used in function<br>&nbsp;<br>&nbsp;if (DebugNewTracer::Ready)<br>&nbsp;&nbsp;DebugNewTrace.Remove (p);<br>&nbsp;free (p);<br>}<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <br>void* operator new [] (size_t size, const char*&nbsp; file, int line)<br>{<br>&nbsp;void * p = malloc (size);<br>&nbsp;if (DebugNewTracer::Ready)<br>&nbsp;&nbsp;DebugNewTrace.Add (p, file, line, size);<br>&nbsp;return p;<br>}</p>
<p>void operator delete [] (void* p, const char* file, int line)<br>{<br>&nbsp;file = 0;&nbsp;// avoid a warning about argument not used in function<br>&nbsp;line = 0;&nbsp;// avoid a warning about argument not used in function<br>&nbsp;<br>&nbsp;if (DebugNewTracer::Ready)<br>&nbsp;&nbsp; DebugNewTrace.Remove (p);<br>&nbsp;free (p);<br>}<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <br>void* operator new (size_t size)<br>{<br>&nbsp;void * p = malloc (size);<br>&nbsp;//&nbsp;When uncommented these lines cause entries in the map for calls to new<br>&nbsp;//&nbsp;that were not altered to the debugnew version. These are likely calls<br>&nbsp;//&nbsp;in library functions and the presence in the dump of these entries<br>&nbsp;//&nbsp;is usually misleading.<br>&nbsp;//&nbsp;if (DebugNewTracer::Ready)<br>&nbsp;//&nbsp;&nbsp; DebugNewTrace.Add (p, "?", 0);<br>&nbsp;return p;<br>}<br>&nbsp;<br>void operator delete (void* p)<br>{<br>&nbsp;if (DebugNewTracer::Ready)<br>&nbsp;&nbsp;DebugNewTrace.Remove (p);<br>&nbsp;free (p);<br>}</p>
<p>//add by yugang<br>void operator delete [] (void* p)<br>{<br>&nbsp;if (DebugNewTracer::Ready)<br>&nbsp;&nbsp;DebugNewTrace.Remove (p);<br>&nbsp;free (p);<br>}<br></p>
</span>
<img src ="http://www.cppblog.com/SpringSnow/aggbug/65738.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/SpringSnow/" target="_blank">Sandy</a> 2008-11-02 10:52 <a href="http://www.cppblog.com/SpringSnow/articles/65738.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>虚继承与虚基类的本质 </title><link>http://www.cppblog.com/SpringSnow/articles/64209.html</link><dc:creator>Sandy</dc:creator><author>Sandy</author><pubDate>Thu, 16 Oct 2008 14:19:00 GMT</pubDate><guid>http://www.cppblog.com/SpringSnow/articles/64209.html</guid><wfw:comment>http://www.cppblog.com/SpringSnow/comments/64209.html</wfw:comment><comments>http://www.cppblog.com/SpringSnow/articles/64209.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/SpringSnow/comments/commentRss/64209.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/SpringSnow/services/trackbacks/64209.html</trackback:ping><description><![CDATA[&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 虚继承与虚基类的本质<br>&nbsp;&nbsp;&nbsp; 虚继承和虚基类的定义是非常的简单的，同时也是非常容易判断一个继承是否是虚继承<br>的，虽然这两个概念的定义是非常的简单明确的，但是在C++语言中虚继承作为一个比较生<br>僻的但是又是绝对必要的组成部份而存在着，并且其行为和模型均表现出和一般的继承体系<br>之间的巨大的差异（包括访问性能上的差异），现在我们就来彻底的从语言、模型、性能和<br>应用等多个方面对虚继承和虚基类进行研究。<br>&nbsp;&nbsp;&nbsp; 首先还是先给出虚继承和虚基类的定义。<br>&nbsp;&nbsp;&nbsp; 虚继承：在继承定义中包含了virtual关键字的继承关系；<br>&nbsp;&nbsp;&nbsp; 虚基类：在虚继承体系中的通过virtual继承而来的基类，需要注意的是：<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; struct CSubClass : public virtual CBase {}; 其中CBase称之为CSubClass<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 的虚基类，而不是说CBase就是个虚基类，因为CBase还可以不不是虚继承体系<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 中的基类。<br>&nbsp;&nbsp;&nbsp; 有了上面的定义后，就可以开始虚继承和虚基类的本质研究了，下面按照语法、语义、<br>模型、性能和应用五个方面进行全面的描述。<br><br>&nbsp;&nbsp;&nbsp; 1. 语法<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 语法有语言的本身的定义所决定，总体上来说非常的简单，如下：<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; struct CSubClass : public virtual CBaseClass {};<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 其中可以采用public、protected、private三种不同的继承关键字进行修饰，只要<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 确保包含virtual就可以了，这样一来就形成了虚继承体系，同时CBaseClass就成为<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 了CSubClass的虚基类了。<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 其实并没有那么的简单，如果出现虚继承体系的进一步继承会出现什么样的状况呢？<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 如下所示：<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; /*<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; * 带有数据成员的基类<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; */<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; struct CBaseClass1<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; CBaseClass1( size_t i ) : m_val( i ) {}<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; size_t m_val;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; };<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; /*<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; * 虚拟继承体系<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; */<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; struct CSubClassV1 : public virtual CBaseClass1<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; CSubClassV1( size_t i ) : CBaseClass1( i ) {}<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; };&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; struct CSubClassV2 : public virtual CBaseClass1<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; CSubClassV2( size_t i ) : CBaseClass1( i ) {}<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; };&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; struct CDiamondClass1 : public CSubClassV1, public CSubClassV2<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; CDiamondClass1( size_t i ) : CBaseClass1( i ), CSubClassV1( i ), CSubClassV2( i ) {}<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; };&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; struct CDiamondSubClass1 : public CDiamondClass1<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; CDiamondSubClass1( size_t i ) : CBaseClass1( i ), CDiamondClass1( i ) {}<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; };<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 注意上面代码中的CDiamondClass1和CDiamondSubClass1两个类的构造函数初始化列<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 表中的内容。可以发现其中均包含了虚基类CBaseClass1的初始化工作，如果没有这<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 个初始化语句就会导致编译时错误，为什么会这样呢？一般情况下不是只要在<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; CSubClassV1和CSubClassV2中包含初始化就可以了么？要解释该问题必须要明白虚<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 继承的语义特征，所以参看下面语义部分的解释。<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp; 2. 语义<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 从语义上来讲什么是虚继承和虚基类呢？上面仅仅是从如何在C++语言中书写合法的<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 虚继承类定义而已。首先来了解一下virtual这个关键字在C++中的公共含义，在C++<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 语言中仅仅有两个地方可以使用virtual这个关键字，一个就是类成员虚函数和这里<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 所讨论的虚继承。不要看这两种应用场合好像没什么关系，其实他们在背景语义上<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 具有virtual这个词所代表的共同的含义，所以才会在这两种场合使用相同的关键字。<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 那么virtual这个词的含义是什么呢？<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; virtual在《美国传统词典[双解]》中是这样定义的：<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; adj.（形容词）<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 1. Existing or resulting in essence or effect though not in actual <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; fact, form, or name:<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 实质上的，实际上的：虽然没有实际的事实、形式或名义，但在实际上或效<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 果上存在或产生的；<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 2. Existing in the mind, especially as a product of the imagination. <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Used in literary criticism of text.<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 虚的，内心的：在头脑中存在的，尤指意想的产物。用于文学批评中。<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 我们采用第一个定义，也就是说被virtual所修饰的事物或现象在本质上是存在的，<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 但是没有直观的形式表现，无法直接描述或定义，需要通过其他的间接方式或手段<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 才能够体现出其实际上的效果。<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 那么在C++中就是采用了这个词意，不可以在语言模型中直接调用或体现的，但是确<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 实是存在可以被间接的方式进行调用或体现的。比如：虚函数必须要通过一种间接的<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 运行时（而不是编译时）机制才能够激活（调用）的函数，而虚继承也是必须在运行<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 时才能够进行定位访问的一种体制。存在，但间接。其中关键就在于存在、间接和共<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 享这三种特征。<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 对于虚函数而言，这三个特征是很好理解的，间接性表明了他必须在运行时根据实际<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 的对象来完成函数寻址，共享性表象在基类会共享被子类重载后的虚函数，其实指向<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 相同的函数入口。<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 对于虚继承而言，这三个特征如何理解呢？存在即表示虚继承体系和虚基类确实存在，<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 间接性表明了在访问虚基类的成员时同样也必须通过某种间接机制来完成（下面模型<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 中会讲到），共享性表象在虚基类会在虚继承体系中被共享，而不会出现多份拷贝。<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 那现在可以解释语法小节中留下来的那个问题了，&#8220;为什么一旦出现了虚基类，就必<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 须在没有一个继承类中都必须包含虚基类的初始化语句&#8221;。由上面的分析可以知道，<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 虚基类是被共享的，也就是在继承体系中无论被继承多少次，对象内存模型中均只会<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 出现一个虚基类的子对象（这和多继承是完全不同的），这样一来既然是共享的那么<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 每一个子类都不会独占，但是总还是必须要有一个类来完成基类的初始化过程（因为<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 所有的对象都必须被初始化，哪怕是默认的），同时还不能够重复进行初始化，那到<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 底谁应该负责完成初始化呢？C++标准中（也是很自然的）选择在每一次继承子类中<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 都必须书写初始化语句（因为每一次继承子类可能都会用来定义对象），而在最下层<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 继承子类中实际执行初始化过程。所以上面在每一个继承类中都要书写初始化语句，<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 但是在创建对象时，而仅仅会在创建对象用的类构造函数中实际的执行初始化语句，<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 其他的初始化语句都会被压制不调用。<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp; 3. 模型<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 为了实现上面所说的三种语义含义，在考虑对象的实现模型（也就是内存模型）时就<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 很自然了。在C++中对象实际上就是一个连续的地址空间的语义代表，我们来分析虚<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 继承下的内存模型。<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 3.1. 存在<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 也就是说在对象内存中必须要包含虚基类的完整子对象，以便能够完成通过地址<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 完成对象的标识。那么至于虚基类的子对象会存放在对象的那个位置（头、中间、<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 尾部）则由各个编译器选择，没有差别。（在VC8中无论虚基类被声明在什么位置，<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 虚基类的子对象都会被放置在对象内存的尾部）<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 3.2. 间接<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 间接性表明了在直接虚基承子类中一定包含了某种指针（偏移或表格）来完成通<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 过子类访问虚基类子对象（或成员）的间接手段（因为虚基类子对象是共享的，<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 没有确定关系），至于采用何种手段由编译器选择。（在VC8中在子类中放置了<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 一个虚基类指针vbc，该指针指向虚函数表中的一个slot，该slot中存放则虚基<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 类子对象的偏移量的负值，实际上就是个以补码表示的int类型的值，在计算虚<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 基类子对象首地址时，需要将该偏移量取绝对值相加，这个主要是为了和虚表<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 中只能存放虚函数地址这一要求相区别，因为地址是原码表示的无符号int类型<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 的值）<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 3.3. 共享<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 共享表明了在对象的内存空间中仅仅能够包含一份虚基类的子对象，并且通过<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 某种间接的机制来完成共享的引用关系。在介绍完整个内容后会附上测试代码，<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 体现这些内容。<br>&nbsp;&nbsp;&nbsp; 4. 性能<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 由于有了间接性和共享性两个特征，所以决定了虚继承体系下的对象在访问时必然<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 会在时间和空间上与一般情况有较大不同。<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 4.1. 时间<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 在通过继承类对象访问虚基类对象中的成员（包括数据成员和函数成员）时，都<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 必须通过某种间接引用来完成，这样会增加引用寻址时间（就和虚函数一样），<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 其实就是调整this指针以指向虚基类对象，只不过这个调整是运行时间接完成的。<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; （在VC8中通过打开汇编输出，可以查看*.cod文件中的内容，在访问虚基类对象<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 成员时会形成三条mov间接寻址语句，而在访问一般继承类对象时仅仅只有一条mov<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 常量直接寻址语句）<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 4.2. 空间<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 由于共享所以不同在对象内存中保存多份虚基类子对象的拷贝，这样较之多继承<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 节省空间。<br>&nbsp;&nbsp;&nbsp; 5. 应用<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 谈了那么多语言特性和内容，那么在什么情况下需要使用虚继承，而一般应该如何使<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 用呢？<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 这个问题其实很难有答案，一般情况下如果你确性出现多继承没有必要，必须要共享<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 基类子对象的时候可以考虑采用虚继承关系（C++标准ios体系就是这样的）。由于每<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 一个继承类都必须包含初始化语句而又仅仅只在最底层子类中调用，这样可能就会使<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 得某些上层子类得到的虚基类子对象的状态不是自己所期望的（因为自己的初始化语<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 句被压制了），所以一般建议不要在虚基类中包含任何数据成员（不要有状态），只<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 可以作为接口类来提供。<br><br>附录：测试代码<br>#include &lt;ctime&gt;<br>#include &lt;iostream&gt;<br><br>/*<br>&nbsp;* 带有数据成员的基类<br>&nbsp;*/<br>struct CBaseClass1<br>{<br>&nbsp;&nbsp;&nbsp; CBaseClass1( size_t i ) : m_val( i ) {}<br><br>&nbsp;&nbsp;&nbsp; size_t m_val;<br>};<br>/*<br>&nbsp;* 虚拟继承体系<br>&nbsp;*/<br>struct CSubClassV1 : public virtual CBaseClass1<br>{<br>&nbsp;&nbsp;&nbsp; CSubClassV1( size_t i ) : CBaseClass1( i ) {}<br>};<br><br>struct CSubClassV2 : public virtual CBaseClass1<br>{<br>&nbsp;&nbsp;&nbsp; CSubClassV2( size_t i ) : CBaseClass1( i ) {}<br>};<br><br>struct CDiamondClass1 : public CSubClassV1, public CSubClassV2<br>{<br>&nbsp;&nbsp;&nbsp; CDiamondClass1( size_t i ) : CBaseClass1( i ), CSubClassV1( i ), CSubClassV2( i ) {}<br>};<br><br>struct CDiamondSubClass1 : public CDiamondClass1<br>{<br>&nbsp;&nbsp;&nbsp; CDiamondSubClass1( size_t i ) : CBaseClass1( i ), CDiamondClass1( i ) {}<br>};<br>/*<br>&nbsp;* 正常继承体系<br>&nbsp;*/<br>struct CSubClassN1 : public CBaseClass1<br>{<br>&nbsp;&nbsp;&nbsp; CSubClassN1( size_t i ) : CBaseClass1( i ) {}<br>};<br>struct CSubClassN2 : public CBaseClass1<br>{<br>&nbsp;&nbsp;&nbsp; CSubClassN2( size_t i ) : CBaseClass1( i ) {}<br>};<br>struct CMultiClass1 : public CSubClassN1, public CSubClassN2<br>{<br>&nbsp;&nbsp;&nbsp; CMultiClass1( size_t i ) : CSubClassN1( i ), CSubClassN2( i ) {}<br>};<br>struct CMultiSubClass1 : public CMultiClass1<br>{<br>&nbsp;&nbsp;&nbsp; CMultiSubClass1( size_t i ) : CMultiClass1( i ) {}<br>};<br>/*<br>&nbsp;* 不带有数据成员的接口基类<br>&nbsp;*/<br>struct CBaseClass2<br>{<br>&nbsp;&nbsp;&nbsp; virtual void func() {};<br>&nbsp;&nbsp;&nbsp; virtual ~CBaseClass2() {}<br>};<br>/*<br>&nbsp;* 虚拟继承体系<br>&nbsp;*/<br>// struct CBaseClassX { CBaseClassX() {i1 = i2 = 0xFFFFFFFF;} size_t i1, i2;};<br>struct CSubClassV3 : public virtual CBaseClass2<br>{<br>};<br>struct CSubClassV4 : public virtual CBaseClass2<br>{<br>};<br>struct CDiamondClass2 : public CSubClassV3, public CSubClassV4<br>{<br>};<br>struct CDiamondSubClass2 : public CDiamondClass2<br>{<br>};<br><br>/*<br>&nbsp;* 正常继承体系<br>&nbsp;*/<br>struct CSubClassN3 : public CBaseClass2<br>{<br>};<br>struct CSubClassN4 : public CBaseClass2<br>{<br>};<br>struct CMultiClass2 : public CSubClassN3, public CSubClassN4<br>{<br>};<br>struct CMultiSubClass2 : public CMultiClass2<br>{<br>};<br><br>/*<br>&nbsp;* 内存布局用类声明.<br>&nbsp;*/<br>struct CLayoutBase1<br>{<br>&nbsp;&nbsp;&nbsp; CLayoutBase1() : m_val1( 0 ), m_val2( 1 ) {}<br><br>&nbsp;&nbsp;&nbsp; size_t m_val1, m_val2;<br>};<br>struct CLayoutBase2<br>{<br>&nbsp;&nbsp;&nbsp; CLayoutBase2() : m_val1( 3 ) {}<br><br>&nbsp;&nbsp;&nbsp; size_t m_val1;<br>};<br>struct CLayoutSubClass1 : public virtual CBaseClass1, public CLayoutBase1, public CLayoutBase2<br>{<br>&nbsp;&nbsp;&nbsp; CLayoutSubClass1() : CBaseClass1( 2 ) {}<br>};<br><br><br>#define MAX_TEST_COUNT 1000 * 1000 * 16<br>#define TIME_ELAPSE() ( std::clock() - start * 1.0 ) / CLOCKS_PER_SEC<br><br>int main( int argc, char *argv[] )<br>{<br>&nbsp;&nbsp;&nbsp; /*<br>&nbsp;&nbsp;&nbsp;&nbsp; * 类体系中的尺寸.<br>&nbsp;&nbsp;&nbsp;&nbsp; */<br>&nbsp;&nbsp;&nbsp; std::cout &lt;&lt; "================================ sizeof ================================" &lt;&lt; std::endl;<br>&nbsp;&nbsp;&nbsp; std::cout &lt;&lt; "&nbsp;&nbsp;&nbsp; ----------------------------------------------------------------" &lt;&lt; std::endl;<br>&nbsp;&nbsp;&nbsp; std::cout &lt;&lt; "sizeof( CBaseClass1 )&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; = " &lt;&lt; sizeof( CBaseClass1 ) &lt;&lt; std::endl;<br>&nbsp;&nbsp;&nbsp; std::cout &lt;&lt; std::endl;<br>&nbsp;&nbsp;&nbsp; std::cout &lt;&lt; "sizeof( CSubClassV1 )&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; = " &lt;&lt; sizeof( CSubClassV1 ) &lt;&lt; std::endl;<br>&nbsp;&nbsp;&nbsp; std::cout &lt;&lt; "sizeof( CSubClassV2 )&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; = " &lt;&lt; sizeof( CSubClassV2 ) &lt;&lt; std::endl;<br>&nbsp;&nbsp;&nbsp; std::cout &lt;&lt; "sizeof( CDiamondClass1 )&nbsp;&nbsp;&nbsp; = " &lt;&lt; sizeof( CDiamondClass1 ) &lt;&lt; std::endl;<br>&nbsp;&nbsp;&nbsp; std::cout &lt;&lt; "sizeof( CDiamondSubClass1 ) = " &lt;&lt; sizeof( CDiamondSubClass1 ) &lt;&lt; std::endl;<br>&nbsp;&nbsp;&nbsp; std::cout &lt;&lt; std::endl;<br>&nbsp;&nbsp;&nbsp; std::cout &lt;&lt; "sizeof( CSubClassN1 )&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; = " &lt;&lt; sizeof( CSubClassN1 ) &lt;&lt; std::endl;<br>&nbsp;&nbsp;&nbsp; std::cout &lt;&lt; "sizeof( CSubClassN2 )&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; = " &lt;&lt; sizeof( CSubClassN2 ) &lt;&lt; std::endl;<br>&nbsp;&nbsp;&nbsp; std::cout &lt;&lt; "sizeof( CMultiClass1 )&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; = " &lt;&lt; sizeof( CMultiClass1 ) &lt;&lt; std::endl;<br>&nbsp;&nbsp;&nbsp; std::cout &lt;&lt; "sizeof( CMultiSubClass1 )&nbsp;&nbsp; = " &lt;&lt; sizeof( CMultiSubClass1 ) &lt;&lt; std::endl;<br><br>&nbsp;&nbsp;&nbsp; std::cout &lt;&lt; "&nbsp;&nbsp;&nbsp; ----------------------------------------------------------------" &lt;&lt; std::endl;<br>&nbsp;&nbsp;&nbsp; std::cout &lt;&lt; "sizeof( CBaseClass2 )&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; = " &lt;&lt; sizeof( CBaseClass2 ) &lt;&lt; std::endl;<br>&nbsp;&nbsp;&nbsp; std::cout &lt;&lt; std::endl;<br>&nbsp;&nbsp;&nbsp; std::cout &lt;&lt; "sizeof( CSubClassV3 )&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; = " &lt;&lt; sizeof( CSubClassV3 ) &lt;&lt; std::endl;<br>&nbsp;&nbsp;&nbsp; std::cout &lt;&lt; "sizeof( CSubClassV4 )&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; = " &lt;&lt; sizeof( CSubClassV4 ) &lt;&lt; std::endl;<br>&nbsp;&nbsp;&nbsp; std::cout &lt;&lt; "sizeof( CDiamondClass2 )&nbsp;&nbsp;&nbsp; = " &lt;&lt; sizeof( CDiamondClass2 ) &lt;&lt; std::endl;<br>&nbsp;&nbsp;&nbsp; std::cout &lt;&lt; "sizeof( CDiamondSubClass2 ) = " &lt;&lt; sizeof( CDiamondSubClass2 ) &lt;&lt; std::endl;<br>&nbsp;&nbsp;&nbsp; std::cout &lt;&lt; std::endl;<br>&nbsp;&nbsp;&nbsp; std::cout &lt;&lt; "sizeof( CSubClassN3 )&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; = " &lt;&lt; sizeof( CSubClassN3 ) &lt;&lt; std::endl;<br>&nbsp;&nbsp;&nbsp; std::cout &lt;&lt; "sizeof( CSubClassN4 )&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; = " &lt;&lt; sizeof( CSubClassN4 ) &lt;&lt; std::endl;<br>&nbsp;&nbsp;&nbsp; std::cout &lt;&lt; "sizeof( CMultiClass2 )&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; = " &lt;&lt; sizeof( CMultiClass2 ) &lt;&lt; std::endl;<br>&nbsp;&nbsp;&nbsp; std::cout &lt;&lt; "sizeof( CMultiSubClass2 )&nbsp;&nbsp; = " &lt;&lt; sizeof( CMultiSubClass2 ) &lt;&lt; std::endl;<br>&nbsp;&nbsp;&nbsp; /*<br>&nbsp;&nbsp;&nbsp;&nbsp; * 对象内存布局<br>&nbsp;&nbsp;&nbsp;&nbsp; */<br>&nbsp;&nbsp;&nbsp; std::cout &lt;&lt; "================================ layout ================================" &lt;&lt; std::endl;<br>&nbsp;&nbsp;&nbsp; std::cout &lt;&lt; "&nbsp;&nbsp;&nbsp; --------------------------------MI------------------------------" &lt;&lt; std::endl;<br>&nbsp;&nbsp;&nbsp; CLayoutSubClass1 *lsc = new CLayoutSubClass1;<br>&nbsp;&nbsp;&nbsp; std::cout &lt;&lt; "sizeof( CLayoutSubClass1 )&nbsp;&nbsp; = " &lt;&lt; sizeof( CLayoutSubClass1 ) &lt;&lt; std::endl;<br>&nbsp;&nbsp;&nbsp; std::cout &lt;&lt; "CLayoutBase1 offset of CLayoutSubClass1 is " &lt;&lt; (char*)(CLayoutBase1 *)lsc - (char*)lsc &lt;&lt; std::endl;<br>&nbsp;&nbsp;&nbsp; std::cout &lt;&lt; "CBaseClass1&nbsp; offset of CLayoutSubClass1 is " &lt;&lt; (char*)(CBaseClass1&nbsp; *)lsc - (char*)lsc &lt;&lt; std::endl;<br>&nbsp;&nbsp;&nbsp; std::cout &lt;&lt; "CLayoutBase2 offset of CLayoutSubClass1 is " &lt;&lt; (char*)(CLayoutBase2 *)lsc - (char*)lsc &lt;&lt; std::endl;<br><br>&nbsp;&nbsp;&nbsp; int *ptr = (int*)lsc;<br>&nbsp;&nbsp;&nbsp; std::cout &lt;&lt; "vbc in CLayoutSubClass1 is " &lt;&lt; *(int*)ptr[3] &lt;&lt; std::endl;<br><br>&nbsp;&nbsp;&nbsp; delete lsc;<br><br>&nbsp;&nbsp;&nbsp; std::cout &lt;&lt; "&nbsp;&nbsp;&nbsp; --------------------------------SI------------------------------" &lt;&lt; std::endl;<br>&nbsp;&nbsp;&nbsp; CSubClassV1 *scv1 = new CSubClassV1( 1 );<br>&nbsp;&nbsp;&nbsp; std::cout &lt;&lt; "sizeof( CSubClassV1 )&nbsp;&nbsp; = " &lt;&lt; sizeof( CSubClassV1 ) &lt;&lt; std::endl;<br>&nbsp;&nbsp;&nbsp; std::cout &lt;&lt; "CBaseClass1 offset of CSubClassV1 is " &lt;&lt; (char*)(CBaseClass1 *)scv1 - (char*)scv1 &lt;&lt; std::endl;<br><br>&nbsp;&nbsp;&nbsp; ptr = (int*)scv1;<br>&nbsp;&nbsp;&nbsp; std::cout &lt;&lt; "vbc in CSubClassV1 is " &lt;&lt; *(int*)ptr[0] &lt;&lt; std::endl;<br><br>&nbsp;&nbsp;&nbsp; delete scv1;<br><br>&nbsp;&nbsp;&nbsp; /*<br>&nbsp;&nbsp;&nbsp;&nbsp; * 性能测试<br>&nbsp;&nbsp;&nbsp;&nbsp; */<br>&nbsp;&nbsp;&nbsp; std::cout &lt;&lt; "================================ Performance ================================" &lt;&lt; std::endl;<br>&nbsp;&nbsp;&nbsp; double times[4];<br>&nbsp;&nbsp;&nbsp; size_t idx = 0;<br><br>&nbsp;&nbsp;&nbsp; CSubClassV1 *ptr1 = new CDiamondClass1( 1 );<br>&nbsp;&nbsp;&nbsp; std::clock_t start = std::clock();<br>&nbsp;&nbsp;&nbsp; {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; for ( size_t i = 0; i &lt; MAX_TEST_COUNT; ++i )<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ptr1-&gt;m_val = i;<br>&nbsp;&nbsp;&nbsp; }<br>&nbsp;&nbsp;&nbsp; times[idx++] = TIME_ELAPSE();<br>&nbsp;&nbsp;&nbsp; delete static_cast&lt;CDiamondClass1*&gt;( ptr1 );<br><br>&nbsp;&nbsp;&nbsp; CSubClassN1 *ptr2 = new CMultiClass1( 0 );<br>&nbsp;&nbsp;&nbsp; start = std::clock();<br>&nbsp;&nbsp;&nbsp; {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; for ( size_t i = 0; i &lt; MAX_TEST_COUNT; ++i )<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ptr2-&gt;m_val = i;<br>&nbsp;&nbsp;&nbsp; }<br>&nbsp;&nbsp;&nbsp; times[idx++] = TIME_ELAPSE();<br>&nbsp;&nbsp;&nbsp; delete static_cast&lt;CMultiClass1*&gt;( ptr2 );<br><br>&nbsp;&nbsp;&nbsp; std::cout &lt;&lt; "CSubClassV1::ptr1-&gt;m_val " &lt;&lt; times[0] &lt;&lt; " s" &lt;&lt; std::endl;<br>&nbsp;&nbsp;&nbsp; std::cout &lt;&lt; "CSubClassN1::ptr2-&gt;m_val " &lt;&lt; times[1] &lt;&lt; " s" &lt;&lt; std::endl;<br><br>&nbsp;&nbsp;&nbsp; return 0; <br>}<br><br>测试环境：<br>&nbsp;&nbsp;&nbsp; 软件环境：Visual Studio2005 Pro + SP1, boost1.34.0<br>&nbsp;&nbsp;&nbsp; 硬件环境：PentiumD 3.0GHz, 4G RAM<br>测试数据：<br>================================ sizeof ================================<br>&nbsp;&nbsp;&nbsp; ----------------------------------------------------------------<br>sizeof( CBaseClass1 )&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; = 4<br><br>sizeof( CSubClassV1 )&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; = 8<br>sizeof( CSubClassV2 )&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; = 8<br>sizeof( CDiamondClass1 )&nbsp;&nbsp;&nbsp; = 12<br>sizeof( CDiamondSubClass1 ) = 12<br><br>sizeof( CSubClassN1 )&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; = 4<br>sizeof( CSubClassN2 )&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; = 4<br>sizeof( CMultiClass1 )&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; = 8<br>sizeof( CMultiSubClass1 )&nbsp;&nbsp; = 8<br>&nbsp;&nbsp;&nbsp; ----------------------------------------------------------------<br>sizeof( CBaseClass2 )&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; = 4<br><br>sizeof( CSubClassV3 )&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; = 8<br>sizeof( CSubClassV4 )&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; = 8<br>sizeof( CDiamondClass2 )&nbsp;&nbsp;&nbsp; = 12<br>sizeof( CDiamondSubClass2 ) = 12<br><br>sizeof( CSubClassN3 )&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; = 4<br>sizeof( CSubClassN4 )&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; = 4<br>sizeof( CMultiClass2 )&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; = 8<br>sizeof( CMultiSubClass2 )&nbsp;&nbsp; = 8<br>================================ layout ================================<br>&nbsp;&nbsp;&nbsp; --------------------------------MI------------------------------<br>sizeof( CLayoutSubClass1 )&nbsp;&nbsp; = 20<br>CLayoutBase1 offset of CLayoutSubClass1 is 0<br>CBaseClass1&nbsp; offset of CLayoutSubClass1 is 16<br>CLayoutBase2 offset of CLayoutSubClass1 is 8<br>vbc in CLayoutSubClass1 is -12<br>&nbsp;&nbsp;&nbsp; --------------------------------SI------------------------------<br>sizeof( CSubClassV1 )&nbsp;&nbsp; = 8<br>CBaseClass1 offset of CSubClassV1 is 4<br>vbc in CSubClassV1 is 0<br>================================ Performance ================================<br>CSubClassV1::ptr1-&gt;m_val 0.062 s<br>CSubClassN1::ptr2-&gt;m_val 0.016 s<br><br>结果分析：<br>&nbsp;&nbsp;&nbsp; 1. 由于虚继承引入的间接性指针所以导致了虚继承类的尺寸会增加4个字节；<br>&nbsp;&nbsp;&nbsp; 2. 由Layout输出可以看出，虚基类子对象被放在了对象的尾部（偏移为16），并且vbc<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 指针必须紧紧的接在虚基类子对象的前面，所以vbc指针所指向的内容为&#8220;偏移 - 4&#8221;；<br>&nbsp;&nbsp;&nbsp; 3. 由于VC8将偏移放在了虚函数表中，所以为了区分函数地址和偏移，所以偏移是用补<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 码int表示的负值；<br>&nbsp;&nbsp;&nbsp; 4. 间接性可以通过性能来看出，在虚继承体系同通过指针访问成员时的时间一般是一般<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 类访问情况下的4倍左右，符合汇编语言输出文件中的汇编语句的安排。
<img src ="http://www.cppblog.com/SpringSnow/aggbug/64209.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/SpringSnow/" target="_blank">Sandy</a> 2008-10-16 22:19 <a href="http://www.cppblog.com/SpringSnow/articles/64209.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>虚拟继承入门</title><link>http://www.cppblog.com/SpringSnow/articles/64206.html</link><dc:creator>Sandy</dc:creator><author>Sandy</author><pubDate>Thu, 16 Oct 2008 13:48:00 GMT</pubDate><guid>http://www.cppblog.com/SpringSnow/articles/64206.html</guid><wfw:comment>http://www.cppblog.com/SpringSnow/comments/64206.html</wfw:comment><comments>http://www.cppblog.com/SpringSnow/articles/64206.html#Feedback</comments><slash:comments>1</slash:comments><wfw:commentRss>http://www.cppblog.com/SpringSnow/comments/commentRss/64206.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/SpringSnow/services/trackbacks/64206.html</trackback:ping><description><![CDATA[在园里转，看到好文章，摘抄过来。<br>链接：<a href="http://www.cppblog.com/franksunny/archive/2008/10/16/64168.html"><br>http://www.cppblog.com/franksunny/archive/2008/10/16/64168.html</a><br><br>原文：<br>
<p><span>为什么要引入虚拟继承？</span></p>
<p><span>虚拟继承在一般的应用中很少用到，所以也往往被忽视，这也主要是因为在</span><span>C++</span><span>中，多重继承是不推荐的，也并不常用，而一旦离开了多重继承，虚拟继承就完全失去了存在的必要（因为这样只会降低效率和占用更多的空间，关于这一点，我自己还没有太多深刻的理解，有兴趣的可以看网络上白杨的作品</span><span><a href="http://baiy.cn/doc/cpp/RTTI%E3%80%81%E8%99%9A%E5%87%BD%E6%95%B0%E5%92%8C%E8%99%9A%E5%9F%BA%E7%B1%BB%E7%9A%84%E5%BC%80%E9%94%80%E5%88%86%E6%9E%90%E5%92%8C%E4%BD%BF%E7%94%A8%E6%8C%87%E5%AF%BC.htm"><u><font color=#800080><span><span>《</span></span>RTTI<span><span>、虚函数和虚<span>基<span>类的开销分析及使用指导<span>》</span></span></span></span></span></font></u></a></span><span>，说实话我目前还没看得很明白，高人可以指点下我）。</span></p>
<p><span>以下面的一个例子为例：</span></p>
<p><span>#include &lt;iostream.h&gt;</span></p>
<p><span>#include &lt;memory.h&gt;</span></p>
<p><span>class CA</span></p>
<p><span>{</span></p>
<p><span><span>&nbsp;&nbsp;&nbsp; </span><span>int k; //</span></span><span>如果基类没有数据成员，则在这里多重继承编译不会出现二义性</span></p>
<p><span>public:</span></p>
<p><span><span>&nbsp;&nbsp;&nbsp; </span>void f() {cout &lt;&lt; "CA::f" &lt;&lt; endl;}</span></p>
<p><span>};</span></p>
<p>&nbsp;</p>
<p><span>class CB : public CA</span></p>
<p><span>{</span></p>
<p><span>};</span></p>
<p>&nbsp;</p>
<p><span>class CC : public CA</span></p>
<p><span>{</span></p>
<p><span>};</span></p>
<p>&nbsp;</p>
<p><span>class CD : public CB, public CC</span></p>
<p><span>{</span></p>
<p><span>};</span></p>
<p>&nbsp;</p>
<p><span>void main()</span></p>
<p><span>{</span></p>
<p><span><span>&nbsp;&nbsp;&nbsp; </span>CD d;</span></p>
<p><span><span>&nbsp;&nbsp;&nbsp; </span>d.f();</span></p>
<p><span>}</span></p>
<p><span>当编译上述代码时，我们会收到如下的错误提示：</span></p>
<p><span>error C2385: 'CD::f' is ambiguous</span></p>
<p><span>即编译器无法确定你在</span><span>d.f()</span><span>中要调用的函数</span><span>f</span><span>到底是哪一个。这里可能会让人觉得有些奇怪，命名只定义了一个</span><span>CA::f</span><span>，既然大家都派生自</span><span>CA</span><span>，那自然就是调用的</span><span>CA::f</span><span>，为什么还无法确定呢？</span></p>
<p><span>这是因为<span>编译器在进行编译的时候，需要确定子类的函数定义</span>，如</span><span>CA::f</span><span>是确定的，那么在编译</span><span>CB</span><span>、</span><span>CC</span><span>时还需要在编译器的语法树中生成</span><span>CB::f</span><span>，</span><span>CC::f</span><span>等标识，那么，在编译</span><span>CD</span><span>的时候，由于</span><span>CB</span><span>、</span><span>CC</span><span>都有一个函数</span><span>f</span><span>，此时，编译器将试图生成这两个</span><span>CD::f</span><span>标识，显然这时就要报错了。（<span>当我们不使用</span></span><span>CD::f</span><span>的时候，以上标识都不会生成，所以，如果去掉</span><span>d.f()</span><span>一句，程序将顺利通过编译</span><span>）</span></p>
<p>&nbsp;</p>
<p><span>要解决这个问题，有两个方法：</span></p>
<p><span>1</span><span>、重载函数</span><span>f()</span><span>：此时由于我们明确定义了</span><span>CD::f</span><span>，编译器检查到</span><span>CD::f()</span><span>调用时就无需再像上面一样去逐级生成</span><span>CD::f</span><span>标识了；</span></p>
<div>
<p><span>此时</span><span>CD</span><span>的元素结构如下：</span></p>
</div>
<p><span>|CB(CA)|</span></p>
<div>
<p><span>|CC(CA)|</span></p>
</div>
<p><span>故此时的</span><span>sizeof(CD) = 8;</span><span>（</span><span>CB</span><span>、</span><span>CC</span><span>各有一个元素</span><span>k</span><span>）</span></p>
<p><span>2</span><span>、使用虚拟继承：虚拟继承又称作共享继承，这种共享其实也是编译期间实现的，当使用虚拟继承时，上面的程序将变成下面的形式：</span></p>
<p><span>#include &lt;iostream.h&gt;</span></p>
<p><span>#include &lt;memory.h&gt;</span></p>
<p><span>class CA</span></p>
<p><span>{</span></p>
<p><span><span>&nbsp;&nbsp;&nbsp; </span>int k;</span></p>
<p><span>public:</span></p>
<p><span><span>&nbsp;&nbsp;&nbsp; </span>void f() {cout &lt;&lt; "CA::f" &lt;&lt; endl;}</span></p>
<p><span>};</span></p>
<p>&nbsp;</p>
<p><span>class CB : virtual public CA&nbsp;</span><span>//</span><span>也有一种写法是</span><span>class CB : public virtual CA</span></p>
<p><span>{<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span><span>//</span></span><span>实际上这两种方法都可以</span></p>
<p><span>};</span></p>
<p>&nbsp;</p>
<p><span>class CC : virtual public CA</span></p>
<p><span>{</span></p>
<p><span>};</span></p>
<p>&nbsp;</p>
<p><span>class CD : public CB, public CC</span></p>
<p><span>{</span></p>
<p><span>};</span></p>
<p>&nbsp;</p>
<p><span>void main()</span></p>
<p><span>{</span></p>
<p><span><span>&nbsp;&nbsp;&nbsp; </span>CD d;</span></p>
<p><span><span>&nbsp;&nbsp;&nbsp; </span>d.f();</span></p>
<p><span>}</span></p>
<div>
<p><span>此时，当编译器确定</span><span>d.f()</span><span>调用的具体含义时，将生成如下的</span><span>CD</span><span>结构：</span></p>
</div>
<p><span>|CB|</span></p>
<p><span>|CC|</span></p>
<div>
<p><span>|CA|</span></p>
</div>
<p><span>同时，在</span><span>CB</span><span>、</span><span>CC</span><span>中都分别包含了一个指向</span><span>CA</span><span>的虚基类指针列表</span><span>vbptr</span><span>（</span><span>virtual base table pointer</span><span>），其中记录的是从</span><span>CB</span><span>、</span><span>CC</span><span>的元素到</span><span>CA</span><span>的元素之间的偏移量。此时，不会生成各子类的函数</span><span>f</span><span>标识，除非子类重载了该函数，从而达到&#8220;共享&#8221;的目的（这里的具体内存布局，可以参看钻石型继承内存布局，在白杨的那篇文章中也有）。</span></p>
<p><span>也正因此，此时的</span><span>sizeof(CD) = 12</span><span>（两个</span><span>vbptr + sizoef(int)</span><span>）</span><span>;</span></p>
<p>&nbsp;</p>
<p><span>另注：</span></p>
<p><span>如果</span><span>CB</span><span>，</span><span>CC</span><span>中各定义一个</span><span>int</span><span>型变量，则</span><span>sizeof(CD)</span><span>就变成</span><span>20(</span><span>两个</span><span>vbptr + 3</span><span>个</span><span>sizoef(int)</span></p>
<p><span>如果</span><span>CA</span><span>中添加一个</span><span>virtual void f1(){}</span><span>，</span><span>sizeof(CD) = 16</span><span>（两个</span><span>vbptr + sizoef(int)+vptr</span><span>）</span><span>;</span></p>
<p><span>再添加</span><span>virtual void f2(){}</span><span>，</span><span>sizeof(CD) = 16</span><span>不变。原因如下所示：带有虚函数的类，其内存布局上包含一个指向虚函数列表的指针（</span><span>vptr</span><span>），这跟有几个虚函数无关。</span></p>
<p><span>以上内容涉及到类对象内存布局问题，本人还难以做过多展开，先贴这么多，本篇文章只是考虑对于虚拟继承进行入门，至于效率、应用等未作展开。本文在网上文章基础上修改了下而得此篇，原文载于</span><span><a href="http://blog.csdn.net/billdavid/archive/2004/06/23/24317.aspx"><u><font color=#800080>http://blog.csdn.net/billdavid/archive/2004/06/23/24317.aspx</font></u></a></span></p>
<p><span>另外关于虚继承和虚基类的讨论，博客园有篇文章《</span><span><a href="http://www.cppblog.com/chemz/archive/2007/06/12/26135.html"><span><span><u><font color=#800080>虚继承与虚基类的本质</font></u></span></span></a></span><span>》，总结得更为详细一点。</span></p>
<img src ="http://www.cppblog.com/SpringSnow/aggbug/64206.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/SpringSnow/" target="_blank">Sandy</a> 2008-10-16 21:48 <a href="http://www.cppblog.com/SpringSnow/articles/64206.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item></channel></rss>