﻿<?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++博客-芳草春晖-文章分类-通用技术</title><link>http://www.cppblog.com/CrazyDev/category/13654.html</link><description>偶尔记录自己思绪的地方...</description><language>zh-cn</language><lastBuildDate>Wed, 19 May 2010 03:59:42 GMT</lastBuildDate><pubDate>Wed, 19 May 2010 03:59:42 GMT</pubDate><ttl>60</ttl><item><title>详解C++的static_cast、dynamic_cast、reinterpret_cast、和const_cast</title><link>http://www.cppblog.com/CrazyDev/articles/115699.html</link><dc:creator>CrazyDev</dc:creator><author>CrazyDev</author><pubDate>Tue, 18 May 2010 09:20:00 GMT</pubDate><guid>http://www.cppblog.com/CrazyDev/articles/115699.html</guid><wfw:comment>http://www.cppblog.com/CrazyDev/comments/115699.html</wfw:comment><comments>http://www.cppblog.com/CrazyDev/articles/115699.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/CrazyDev/comments/commentRss/115699.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/CrazyDev/services/trackbacks/115699.html</trackback:ping><description><![CDATA[关于强制类型转换的问题，很多书都讨论过，写的最详细的是C++ 之父的《C++ 的设计和演化》。最好的解决方法就是不要使用C风格的强制类型转换，而是使用标准C++的类型转换符：static_cast, dynamic_cast。标准C++中有四个类型转换符：<strong>static_cast</strong>、<strong>dynamic_cast</strong>、<strong>reinterpret_cast</strong>、和<strong>const_cast</strong>。下面对它们一一进行介绍。<br><br><span style="FONT-SIZE: 16pt"><strong>static_cast</strong></span><br><br>用法：<strong>static_cast </strong>&lt; type-id &gt; ( expression )<br><br>该运算符把expression转换为type-id类型，但没有运行时类型检查来保证转换的安全性。它主要有如下几种用法：
<ul class=ubb-list>
    <li>用于类层次结构中基类和子类之间指针或引用的转换。进行上行转换（把子类的指针或引用转换成基类表示）是安全的；进行下行转换（把基类指针或引用转换成子类表示）时，由于没有动态类型检查，所以是不安全的。
    <li>用于基本数据类型之间的转换，如把int转换成char，把int转换成enum。这种转换的安全性也要开发人员来保证。
    <li>把空指针转换成目标类型的空指针。
    <li>把任何类型的表达式转换成void类型。</li>
</ul>
注意：static_cast不能转换掉expression的const、volitale、或者__unaligned属性。<br><br><span style="FONT-SIZE: 16pt"><strong>dynamic_cast</strong></span><br><br>用法：<strong>dynamic_cast </strong>&lt; type-id &gt; ( expression )<br><br>该运算符把expression转换成type-id类型的对象。Type-id必须是类的指针、类的引用或者void *；如果type-id是类指针类型，那么expression也必须是一个指针，如果type-id是一个引用，那么expression也必须是一个引用。<br><br>dynamic_cast主要用于类层次间的上行转换和下行转换，还可以用于类之间的交叉转换。<br><br>在类层次间进行上行转换时，dynamic_cast和static_cast的效果是一样的；在进行下行转换时，dynamic_cast具有类型检查的功能，比static_cast更安全。<br>
<div class=code>&nbsp;class B{<br><br>public:<br><br>&nbsp;int m_iNum;<br><br>&nbsp;virtual void foo();<br><br>};<br><br>class D:public B{<br><br>&nbsp;public:<br><br>&nbsp;char *m_szName[100];<br><br>};<br><br>&nbsp;<br><br>void func(B *pb){<br><br>&nbsp;D *pd1 = static_cast&lt;D *&gt;(pb);<br><br>&nbsp;D *pd2 = dynamic_cast&lt;D *&gt;(pb);<br><br>}</div>
<br>在上面的代码段中，如果pb指向一个D类型的对象，pd1和pd2是一样的，并且对这两个指针执行D类型的任何操作都是安全的；但是，如果pb指向的是一个B类型的对象，那么pd1将是一个指向该对象的指针，对它进行D类型的操作将是不安全的（如访问m_szName），而pd2将是一个空指针。另外要注意：B要有虚函数，否则会编译出错；static_cast则没有这个限制。这是由于运行时类型检查需要运行时类型信息，而这个信息存储在类的虚函数表（关于虚函数表的概念，详细可见&lt;Inside c++ object model&gt;）中，只有定义了虚函数的类才有虚函数表，没有定义虚函数的类是没有虚函数表的。<br><br>另外，<strong>dynamic_cast</strong>还支持交叉转换（cross cast）。如下代码所示。<br>
<div class=code>class A{<br><br>public:<br><br>&nbsp;int m_iNum;<br><br>&nbsp;virtual void f(){}<br><br>};<br><br>&nbsp;<br><br>class B:public A{<br><br>};<br><br>&nbsp;<br><br>class D:public A{<br><br>};<br><br>&nbsp;<br><br>void foo(){<br><br>&nbsp;B *pb = new B;<br><br>&nbsp;pb-&gt;m_iNum = 100;<br><br>&nbsp;D *pd1 = static_cast&lt;D *&gt;(pb); //copile error<br><br>&nbsp;D *pd2 = dynamic_cast&lt;D *&gt;(pb); //pd2 is NULL<br><br>&nbsp;delete pb;<br><br>}</div>
<br>在函数foo中，使用<strong>static_cast</strong>进行转换是不被允许的，将在编译时出错；而使用 <strong>dynamic_cast</strong>的转换则是允许的，结果是空指针。<br><br><span style="FONT-SIZE: 16pt"><strong>reinpreter_cast</strong></span><br><br>用法：<strong>reinpreter_cast</strong>&lt;type-id&gt; (expression)<br><br>type-id必须是一个指针、引用、算术类型、函数指针或者成员指针。它可以把一个指针转换成一个整数，也可以把一个整数转换成一个指针（先把一个指针转换成一个整数，在把该整数转换成原类型的指针，还可以得到原先的指针值）。<br><br>该运算符的用法比较多。<br><br><span style="FONT-SIZE: 16pt"><strong>const_cast</strong></span><br><br>用法：<strong>const_cast</strong>&lt;type_id&gt; (expression)<br><br>该运算符用来修改类型的const或volatile属性。除了const 或volatile修饰之外， type_id和expression的类型是一样的。<br><br>常量指针被转化成非常量指针，并且仍然指向原来的对象；常量引用被转换成非常量引用，并且仍然指向原来的对象；常量对象被转换成非常量对象。<br><br>Voiatile和const类试。举如下一例：<br>
<div class=code>class B{<br><br>&nbsp;public:<br><br>&nbsp;int m_iNum;<br><br>}<br><br>void foo(){<br><br>const B b1;<br><br>b1.m_iNum = 100; //comile error<br><br>B b2 = const_cast&lt;B&gt;(b1);<br><br>b2. m_iNum = 200; //fine<br>&nbsp;}</div>
<br>上面的代码编译时会报错，因为b1是一个常量对象，不能对它进行改变；使用const_cast把它转换成一个常量对象，就可以对它的数据成员任意改变。注意：b1和b2是两个不同的对象。
<img src ="http://www.cppblog.com/CrazyDev/aggbug/115699.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/CrazyDev/" target="_blank">CrazyDev</a> 2010-05-18 17:20 <a href="http://www.cppblog.com/CrazyDev/articles/115699.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Bjarne Stroustrup的FAQ：C++的风格与技巧</title><link>http://www.cppblog.com/CrazyDev/articles/115698.html</link><dc:creator>CrazyDev</dc:creator><author>CrazyDev</author><pubDate>Tue, 18 May 2010 09:12:00 GMT</pubDate><guid>http://www.cppblog.com/CrazyDev/articles/115698.html</guid><wfw:comment>http://www.cppblog.com/CrazyDev/comments/115698.html</wfw:comment><comments>http://www.cppblog.com/CrazyDev/articles/115698.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/CrazyDev/comments/commentRss/115698.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/CrazyDev/services/trackbacks/115698.html</trackback:ping><description><![CDATA[翻译：左轻侯<br><br>（译注：本文的翻译相当艰苦。Bjarne Stroustrup不愧是创立C++语言的一代大师，不但思想博大精深，而且在遣词造句上，也非常精微深奥。有很多地方，译者反复斟酌，都不能取得理想的效果，只能尽力而为。<br><br>Html格式的文档见译者主页：<a title=http://www.wushuang.net href="http://www.wushuang.net/" target=_blank>http://www.wushuang.net</a><br><br>如果你对这个翻译稿有任何意见和建议，请发信给译者：onekey@163.com。<br><br>原文的地址为：<a title=http://www.research.att.com/~bs/bs_faq2.html） href="http://www.research.att.com/~bs/bs_faq2.html）" target=_blank>http://www.research.att.com/~bs/bs_faq2.html）</a><br><br>（Bjarne Stroustrup博士，1950年出生于丹麦，先后毕业于丹麦阿鲁斯大学和英国剑挢大学，AT&amp;T大规模程序设计研究部门负责人，AT&amp;T 贝尔实验室和ACM成员。1979年，B. S开始开发一种语言，当时称为"C with Class"，后来演化为C++。1998年，ANSI/ISO C++标准建立，同年，B. S推出其经典著作The C++ Programming Language的第三版。）<br><br>这是一些人们经常向我问起的有关C++的风格与技巧的问题。如果你能提出更好的问题，或者对这些答案有所建议，请务必发Email给我(<a href="&#109;&#97;&#105;&#108;&#116;&#111;&#58;&#98;&#115;&#64;&#114;&#101;&#115;&#101;&#97;&#114;&#99;&#104;&#46;&#97;&#116;&#116;&#46;&#99;&#111;&#109;">bs@research.att.com</a>)。请记住，我不能把全部的时间都花在更新我的主页上面。<br><br>更多的问题请参见我的general FAQ。<br><br>关于术语和概念，请参见我的C++术语表（C++ glossary.）。<br><br>请注意，这仅仅是一个常见问题与解答的列表。它不能代替一本优秀教科书中那些经过精心挑选的范例与解释。它也不能象一本参考手册或语言标准那样，提供详细和准确的说明。有关C++的设计的问题，请参见《C++语言的设计和演变》（The Design and Evolution of C++）。关于C++语言与标准库的使用，请参见《C++程序设计语言》（The C++ Programming Language）。<br><br><br>目录：<br><br>我如何写这个非常简单的程序？<br><br>为什么编译要花这么长的时间？<br><br>为什么一个空类的大小不为0？<br><br>我必须在类声明处赋予数据吗？<br><br>为什么成员函数默认不是virtual的？<br><br>为什么析构函数默认不是virtual的？<br><br>为什么不能有虚拟构造函数？<br><br>为什么重载在继承类中不工作？<br><br>我能够在构造函数中调用一个虚拟函数吗？<br><br>有没有&#8220;指定位置删除&#8221;(placement delete)？<br><br>我能防止别人继承我自己的类吗？<br><br>为什么不能为模板参数定义约束（constraints）？<br><br>既然已经有了优秀的qsort()函数，为什么还需要一个sort()？<br><br>什么是函数对象（function object）？<br><br>我应该如何对付内存泄漏？<br><br>我为什么在捕获一个异常之后就不能继续？<br><br>为什么C++中没有相当于realloc()的函数？<br><br>如何使用异常？<br><br>怎样从输入中读取一个字符串？<br><br>为什么C++不提供&#8220;finally&#8221;的构造？<br><br>什么是自动指针（auto_ptr），为什么没有自动数组（auto_array）？<br><br>可以混合使用C风格与C++风格的内存分派与重新分配吗？<br><br>我为什么必须使用一个造型来转换*void？<br><br>我如何定义一个类内部（in-class）的常量？<br><br>为什么delete不会将操作数置0？<br><br>我能够写&#8220;void main()&#8221;吗？<br><br>为什么我不能重载点符号，::，sizeof，等等？<br><br>怎样将一个整型值转换为一个字符串？<br><br>&#8220;int* p&#8221;正确还是&#8220;int *p&#8221;正确？<br><br>对于我的代码，哪一种布局风格（layout style）是最好的？<br><br>我应该将&#8220;const&#8221;放在类型之前还是之后？<br><br>使用宏有什么问题？<br><br>我如何写这个非常简单的程序？<br><br><br>特别是在一个学期的开始，我常常收到许多关于编写一个非常简单的程序的询问。这个问题有一个很具代表性的解决方法，那就是（在你的程序中）读入几个数字，对它们做一些处理，再把结果输出。下面是一个这样做的例子：<br><br>
<div class=quote>
<div class=quote-title><u></u></div>
<div class=quote-content>#include&lt;iostream&gt;<br><br>#include&lt;vector&gt;<br><br>#include&lt;algorithm&gt;<br><br>usingnamespace std;<br><br>intmain()<br><br>{<br>vector&lt;double&gt;v;<br><br>doubled;<br><br>while(cin&gt;&gt;d)v.push_back(d); // 读入元素<br><br>if(!cin.eof()) { // 检查输入是否出错<br><br>cerr&lt;&lt; "format error\n";<br><br>return1; // 返回一个错误<br><br>}<br><br>cout&lt;&lt; "read " &lt;&lt; v.size() &lt;&lt; " elements\n";<br><br>reverse(v.begin(),v.end());<br><br>cout&lt;&lt; "elements in reverse order:\n";<br><br>for(int i = 0; i&lt;v.size(); ++i) cout &lt;&lt; v[i] &lt;&lt; '\n';<br><br>return0; // 成功返回<br>}</div>
<br>对这段程序的观察：<br>这是一段标准的ISO C++程序，使用了标准库(standard library)。标准库工具在命名空间std中声明，封装在没有.h后缀的头文件中。<br><br>如果你要在Windows下编译它，你需要将它编译成一个&#8220;控制台程序&#8221;（consoleapplication）。记得将源文件加上.cpp后缀，否则编译器可能会以为它是一段C代码而不是C++。<br><br>是的，main()函数返回一个int值。<br><br>读到一个标准的向量(vector)中，可以避免在随意确定大小的缓冲中溢出的错误。读到一个数组(array)中，而不产生&#8220;简单错误&#8221;(silly error)，这已经超出了一个新手的能力——如果你做到了，那你已经不是一个新手了。如果你对此表示怀疑，我建议你阅读我的文章&#8220;将标准C++作为一种新的语言来学习&#8221;("Learning Standard C++as a New Language")，你可以在本人著作列表(my publications list)中下载到它。<br><br>!cin.eof()是对流的格式的检查。事实上，它检查循环是否终结于发现一个end-of-file(如果不是这样，那么意味着输入没有按照给定的格式)。更多的说明，请参见你的C++教科书中的&#8220;流状态&#8221;(stream state)部分。<br><br>vector知道它自己的大小，因此我不需要计算元素的数量。<br><br>这段程序没有包含显式的内存管理。Vector维护一个内存中的栈，以存放它的元素。当一个vector需要更多的内存时，它会分配一些；当它不再生存时，它会释放内存。于是，使用者不需要再关心vector中元素的内存分配和释放问题。<br><br>程序在遇到输入一个&#8220;end-of-file&#8221;时结束。如果你在UNIX平台下运行它，&#8220;end-of-file&#8221;等于键盘上的Ctrl+D。如果你在Windows平台下，那么由于一个BUG它无法辨别&#8220;end-of-file&#8221;字符，你可能倾向于使用下面这个稍稍复杂些的版本，它使用一个词&#8220;end&#8221;来表示输入已经结束。<br></div>
<div class=quote>
<div class=quote-title><u></u></div>
<div class=quote-content>#include&lt;iostream&gt;<br><br>#include&lt;vector&gt;<br><br>#include&lt;algorithm&gt;<br><br>#include&lt;string&gt;<br><br>usingnamespace std;<br><br>intmain()<br><br>{<br>vector&lt;double&gt;v;<br><br>doubled;<br><br>while(cin&gt;&gt;d)v.push_back(d); // 读入一个元素<br><br>if(!cin.eof()) { // 检查输入是否失败<br><br>cin.clear(); //清除错误状态<br><br>strings;<br><br>cin&gt;&gt; s; // 查找结束字符<br><br>if(s != "end") {<br><br>cerr&lt;&lt; "format error\n";<br><br>return1; // 返回错误<br><br>}<br><br>}<br><br>cout&lt;&lt; "read " &lt;&lt; v.size() &lt;&lt; " elements\n";<br><br>reverse(v.begin(),v.end());<br><br>cout&lt;&lt; "elements in reverse order:\n";<br><br>for(int i = 0; i&lt;v.size(); ++i) cout &lt;&lt; v[i] &lt;&lt; '\n';<br><br>return0; // 成功返回<br>}<br><br>更多的关于使用标准库将事情简化的例子，请参见《C++程序设计语言》中的&#8220;漫游标准库&#8221;("Tour of the StandardLibrary")一章。<br>为什么编译要花这么长的时间？<br><br>你的编译器可能有问题。也许它太老了，也许你安装它的时候出了错，也许你用的计算机已经是个古董。在诸如此类的问题上，我无法帮助你。<br><br>但是，这也是很可能的：你要编译的程序设计得非常糟糕，以至于编译器不得不检查数以百计的头文件和数万行代码。理论上来说，这是可以避免的。如果这是你购买的库的设计问题，你对它无计可施（除了换一个更好的库），但你可以将你自己的代码组织得更好一些，以求得将修改代码后的重新编译工作降到最少。这样的设计会更好，更有可维护性，因为它们展示了更好的概念上的分离。<br><br>看看这个典型的面向对象的程序例子：</div>
</div>
<div class=quote>
<div class=quote-title><u></u></div>
<div class=quote-content>classShape {<br><br>public: //使用Shapes的用户的接口<br><br>virtualvoid draw() const;<br><br>virtualvoid rotate(int degrees);<br><br>//...<br><br>protected: //common data (for implementers of Shapes)<br><br>Pointcenter;<br><br>Colorcol;<br><br>//...<br><br>};<br><br>classCircle : public Shape {<br><br>public: <br><br>voiddraw() const;<br><br>voidrotate(int) { }<br><br>//...<br><br>protected:<br><br>intradius;<br><br>//...<br><br>};<br><br>classTriangle : public Shape {<br><br>public: <br><br>voiddraw() const;<br><br>voidrotate(int);<br><br>//...<br><br>protected:<br><br>Pointa, b, c;<br><br>//...<br><br>}; </div>
<br>设计思想是，用户通过Shape的public接口来操纵它们，而派生类（例如Circle和Triangle）的实现部分则共享由protected成员表现的那部分实现（implementation）。<br><br>这不是一件容易的事情：确定哪些实现部分是对所有的派生类都有用的，并将之共享出来。因此，与public接口相比，protected成员往往要做多得多的改动。举例来说，虽然理论上&#8220;中心&#8221;(center)对所有的图形都是一个有效的概念，但当你要维护一个三角形的&#8220;中心&#8221;的时候，是一件非常麻烦的事情——对于三角形，当且仅当它确实被需要的时候，计算这个中心才是有意义的。<br><br>protected成员很可能要依赖于实现部分的细节，而Shape的用户（译注：user此处译为用户，指使用Shape类的代码，下同）却不见得必须依赖它们。举例来说，很多（大多数？）使用Shape的代码在逻辑上是与&#8220;颜色&#8221;无关的，但是由于Shape中&#8220;颜色&#8221;这个定义的存在，却可能需要一堆复杂的头文件，来结合操作系统的颜色概念。<br><br>当protected部分发生了改变时，使用Shape的代码必须重新编译——即使只有派生类的实现部分才能够访问protected成员。<br><br>于是，基类中的&#8220;实现相关的信息&#8221;(informationhelpful to implementers)对用户来说变成了象接口一样敏感的东西，它的存在导致了实现部分的不稳定，用户代码的无谓的重编译（当实现部分发生改变时），以及将头文件无节制地包含进用户代码中（因为&#8220;实现相关的信息&#8221;需要它们）。有时这被称为&#8220;脆弱的基类问题&#8221;(brittle baseclass problem)。<br><br>一个很明显的解决方案就是，忽略基类中那些象接口一样被使用的&#8220;实现相关的信息&#8221;。换句话说，使用接口，纯粹的接口。也就是说，用抽象基类的方式来表示接口：</div>
<div class=quote>
<div class=quote-title><u></u></div>
<div class=quote-content>classShape {<br><br>public: //使用Shapes的用户的接口<br><br>virtualvoid draw() const = 0;<br><br>virtualvoid rotate(int degrees) = 0;<br><br>virtualPoint center() const = 0;<br><br>//...<br><br>//没有数据<br><br>};<br><br><br>classCircle : public Shape {<br><br>public: <br><br>voiddraw() const;<br><br>voidrotate(int) { }<br><br>Pointcenter() const { return center; }<br><br>//...<br><br>protected:<br><br>Pointcent;<br><br>Colorcol;<br><br>intradius;<br><br>//...<br><br>};<br><br><br>classTriangle : public Shape {<br><br>public: <br><br>voiddraw() const;<br><br>voidrotate(int);<br><br>Pointcenter() const;<br><br>//...<br><br>protected:<br><br>Colorcol;<br><br>Pointa, b, c;<br><br>//...<br><br>}; </div>
<br>现在，用户代码与派生类的实现部分的变化之间的关系被隔离了。我曾经见过这种技术使得编译的时间减少了几个数量级。<br><br><br>但是，如果确实存在着对所有派生类（或仅仅对某些派生类）都有用的公共信息时怎么办呢？可以简单把这些信息封装成类，然后从它派生出实现部分的类：<br><br></div>
<div class=quote>
<div class=quote-title><u></u></div>
<div class=quote-content>classShape {<br><br>public: //使用Shapes的用户的接口<br><br>virtualvoid draw() const = 0;<br><br>virtualvoid rotate(int degrees) = 0;<br><br>virtualPoint center() const = 0;<br><br>//...<br><br><br>//no data<br><br>};<br><br>structCommon {<br><br>Colorcol;<br><br>//...<br><br>};<br><br><br>classCircle : public Shape, protected Common {<br><br>public: <br><br>voiddraw() const;<br><br>voidrotate(int) { }<br><br>Pointcenter() const { return center; }<br><br>//...<br><br>protected:<br><br>Pointcent;<br><br>intradius;<br><br>};<br><br><br><br>classTriangle : public Shape, protected Common {<br><br>public: <br><br>voiddraw() const;<br><br>voidrotate(int);<br><br>Pointcenter() const;<br><br>//...<br><br>protected:<br><br>Pointa, b, c;<br><br>}; </div>
</div>
<br><br>为什么一个空类的大小不为0？<br><br><br><br>要清楚，两个不同的对象的地址也是不同的。基于同样的理由，new总是返回指向不同对象的指针。<br><br>看看：<br><br><br><br>
<div class=quote>
<div class=quote-title><u></u></div>
<div class=quote-content>classEmpty { };<br><br><br><br>voidf()<br><br>{<br><br>Emptya, b;<br><br>if(&amp;a == &amp;b) cout &lt;&lt; "impossible: report error to compilersupplier";<br><br><br><br>Empty*p1 = new Empty;<br><br>Empty*p2 = new Empty;<br><br>if(p1 == p2) cout &lt;&lt; "impossible: report error to compilersupplier";<br><br>} </div>
</div>
<br><br>有一条有趣的规则：一个空的基类并不一定有分隔字节。
<div class=quote>
<div class=quote-title><u></u></div>
<div class=quote-content>structX : Empty {<br><br>inta;<br><br>//...<br><br>};<br><br><br><br>voidf(X* p)<br><br>{<br><br>void*p1 = p;<br><br>void*p2 = &amp;p-&gt;a;<br><br>if(p1 == p2) cout &lt;&lt; "nice: good optimizer";<br><br>}</div>
</div>
<br><br>这种优化是允许的，可以被广泛使用。它允许程序员使用空类以表现一些简单的概念。现在有些编译器提供这种&#8220;空基类优化&#8221;(empty base classoptimization)。<br><br><br><br>我必须在类声明处赋予数据吗？<br><br><br><br>不必须。如果一个接口不需要数据时，无须在作为接口定义的类中赋予数据。代之以在派生类中给出它们。参见&#8220;为什么编译要花这么长的时间？&#8221;。<br><br><br><br>有时候，你必须在一个类中赋予数据。考虑一下复数类的情况：<br><br><br><br>
<div class=quote>
<div class=quote-title><u></u></div>
<div class=quote-content>template&lt;classScalar&gt; class complex {<br><br>public:<br><br>complex(): re(0), im(0) { }<br><br>complex(Scalarr) : re(r), im(0) { }<br><br>complex(Scalarr, Scalar i) : re(r), im(i) { }<br><br>//...<br><br><br><br>complex&amp;operator+=(const complex&amp; a)<br><br>{re+=a.re; im+=a.im; return *this; }<br><br>//...<br><br>private:<br><br>Scalarre, im;<br><br>};</div>
</div>
<br><br>设计这种类型的目的是将它当做一个内建（built-in）类型一样被使用。在声明处赋值是必须的，以保证如下可能：建立真正的本地对象（genuinely localobjects）(比如那些在栈中而不是在堆中分配的对象)，或者使某些简单操作被适当地inline化。对于那些支持内建的复合类型的语言来说，要获得它们提供的效率，真正的本地对象和inline化都是必要的。<br><br><br><br>为什么成员函数默认不是virtual的？<br><br><br><br>因为很多类并不是被设计作为基类的。例如复数类。<br><br><br><br>而且，一个包含虚拟函数的类的对象，要占用更多的空间以实现虚拟函数调用机制——往往是每个对象占用一个字(word)。这个额外的字是非常可观的，而且在涉及和其它语言的数据的兼容性时，可能导致麻烦(例如C或Fortran语言)。<br><br><br><br>要了解更多的设计原理，请参见《C++语言的设计和演变》（The Design and Evolution of C++）。<br><br><br><br>为什么析构函数默认不是virtual的？<br><br><br><br>因为很多类并不是被设计作为基类的。只有类在行为上是它的派生类的接口时(这些派生类往往在堆中分配，通过指针或引用来访问)，虚拟函数才有意义。<br><br><br><br>那么什么时候才应该将析构函数定义为虚拟呢？当类至少拥有一个虚拟函数时。拥有虚拟函数意味着一个类是派生类的接口，在这种情况下，一个派生类的对象可能通过一个基类指针来销毁。例如：<br><br><br><br>
<div class=quote>
<div class=quote-title><u></u></div>
<div class=quote-content>classBase {<br><br>//...<br><br>virtual~Base();<br><br>};<br><br><br><br>classDerived : public Base {<br><br>//...<br><br>~Derived();<br><br>};<br><br><br><br>voidf()<br><br>{<br><br>Base*p = new Derived;<br><br>deletep; // 虚拟析构函数保证~Derived函数被调用<br><br>}</div>
</div>
<br><br>如果基类的析构函数不是虚拟的，那么派生类的析构函数将不会被调用——这可能产生糟糕的结果，例如派生类的资源不会被释放。<br><br><br><br>为什么不能有虚拟构造函数？<br><br><br><br>虚拟调用是一种能够在给定信息不完全(given partialinformation)的情况下工作的机制。特别地，虚拟允许我们调用某个函数，对于这个函数，仅仅知道它的接口，而不知道具体的对象类型。但是要建立一个对象，你必须拥有完全的信息。特别地，你需要知道要建立的对象的具体类型。因此，对构造函数的调用不可能是虚拟的。<br><br><br><br>当要求建立一个对象时，一种间接的技术常常被当作&#8220;虚拟构造函数&#8221;来使用。有关例子，请参见《C++程序设计语言》第三版15.6.2.节。<br><br><br><br>下面这个例子展示一种机制：如何使用一个抽象类来建立一个适当类型的对象。<br><br>
<div class=quote>
<div class=quote-title><u></u></div>
<div class=quote-content>structF { // 对象建立函数的接口<br><br>virtualA* make_an_A() const = 0;<br><br>virtualB* make_a_B() const = 0;<br><br>};<br><br><br><br>voiduser(const F&amp; fac)<br><br>{<br><br>A*p = fac.make_an_A(); // 将A作为合适的类型<br><br>B*q = fac.make_a_B(); // 将B作为合适的类型<br><br>//...<br><br>}<br><br><br><br>structFX : F {<br><br>A*make_an_A() const { return new AX(); } // AX是A的派生<br><br>B*make_a_B() const { return new BX(); } // AX是B的派生<br><br>};<br><br><br><br>structFY : F {<br><br>A*make_an_A() const { return new AY(); } // AY是A的派生<br><br>B*make_a_B() const { return new BY(); } // BY是B的派生<br><br><br><br>};<br><br><br><br>intmain()<br><br>{<br><br>user(FX()); //此用户建立AX与BX<br><br>user(FY()); //此用户建立AY与BY<br><br>//...<br><br>}</div>
</div>
<br><br>这是所谓的&#8220;工厂模式&#8221;(the factory pattern)的一个变形。关键在于，user函数与AX或AY这样的类的信息被完全分离开来了。<br><br><br><br>为什么重载在继承类中不工作？<br><br><br><br>这个问题（非常常见）往往出现于这样的例子中：<br><br>
<div class=quote>
<div class=quote-title><u></u></div>
<div class=quote-content>#include&lt;iostream&gt;<br><br>usingnamespace std;<br><br><br><br>classB {<br><br>public:<br><br>intf(int i) { cout &lt;&lt; "f(int): "; return i+1; }<br><br>//...<br><br>};<br><br><br><br>classD : public B {<br><br>public:<br><br>doublef(double d) { cout &lt;&lt; "f(double): "; return d+1.3; }<br><br>//...<br><br>};<br><br><br><br>intmain()<br><br>{<br><br>D*pd = new D;<br><br><br><br>cout&lt;&lt; pd-&gt;f(2) &lt;&lt; '\n';<br><br>cout&lt;&lt; pd-&gt;f(2.3) &lt;&lt; '\n';<br><br>}</div>
</div>
<br><br>它输出的结果是：<br><br><br><br>
<div class=quote>
<div class=quote-title><u></u></div>
<div class=quote-content>f(double):3.3<br><br>f(double):3.6</div>
</div>
<br><br>而不是象有些人猜想的那样：<br><br><br><br>
<div class=quote>
<div class=quote-title><u></u></div>
<div class=quote-content>f(int):3<br><br>f(double):3.6</div>
</div>
<br><br>换句话说，在B和D之间并没有发生重载的解析。编译器在D的区域内寻找，找到了一个函数double f(double)，并执行了它。它永远不会涉及（被封装的）B的区域。在C++中，没有跨越区域的重载——对于这条规则，继承类也不例外。更多的细节，参见《C++语言的设计和演变》和《C++程序设计语言》。<br><br><br><br>但是，如果我需要在基类和继承类之间建立一组重载的f()函数呢？很简单，使用using声明：<br><br>
<div class=quote>
<div class=quote-title><u></u></div>
<div class=quote-content>classD : public B {<br><br>public:<br><br>usingB::f; // make every f from B available<br><br>doublef(double d) { cout &lt;&lt; "f(double): "; return d+1.3; }<br><br>//...<br><br>};</div>
</div>
<br><br>进行这个修改之后，输出结果将是：<br><br>
<div class=quote>
<div class=quote-title><u></u></div>
<div class=quote-content>f(int):3<br><br>f(double):3.6</div>
</div>
<br><br>这样，在B的f()和D的f()之间，重载确实实现了，并且选择了一个最合适的f()进行调用。<br><br><br><br>我能够在构造函数中调用一个虚拟函数吗？<br><br><br><br>可以，但是要小心。它可能不象你期望的那样工作。在构造函数中，虚拟调用机制不起作用，因为继承类的重载还没有发生。对象先从基类被创建，&#8220;基类先于继承类(base beforederived)&#8221;。<br><br><br><br>看看这个：<br><br>
<div class=quote>
<div class=quote-title><u></u></div>
<div class=quote-content>#include&lt;string&gt;<br><br>#include&lt;iostream&gt;<br><br>usingnamespace std;<br><br><br><br>classB {<br><br>public:<br><br>B(conststring&amp; ss) { cout &lt;&lt; "B constructor\n"; f(ss); }<br><br>virtualvoid f(const string&amp;) { cout &lt;&lt; "B::f\n";}<br><br>};<br><br><br><br>classD : public B {<br><br>public:<br><br>D(conststring &amp; ss) :B(ss) { cout &lt;&lt; "D constructor\n";}<br><br>voidf(const string&amp; ss) { cout &lt;&lt; "D::f\n"; s = ss; }<br><br>private:<br><br>strings;<br><br>};<br><br><br><br>intmain()<br><br>{<br><br>Dd("Hello");<br><br>}</div>
</div>
<br><br>程序编译以后会输出：<br><br>
<div class=quote>
<div class=quote-title><u></u></div>
<div class=quote-content>Bconstructor<br><br>B::f<br><br>Dconstructor</div>
</div>
<br><br>注意不是D::f。设想一下，如果出于不同的规则，B::B()可以调用D::f()的话，会产生什么样的后果：因为构造函数D::D()还没有运行，D::f()将会试图将一个还没有初始化的字符串s赋予它的参数。结果很可能是导致立即崩溃。<br><br><br><br>析构函数在&#8220;继承类先于基类&#8221;的机制下运行，因此虚拟机制的行为和构造函数一样：只有本地定义(local definitions)被使用——不会调用虚拟函数，以免触及对象中的（现在已经被销毁的）继承类的部分。<br><br><br><br>更多的细节，参见《C++语言的设计和演变》13.2.4.2和《C++程序设计语言》15.4.3。<br><br><br><br>有人暗示，这只是一条实现时的人为制造的规则。不是这样的。事实上，要实现这种不安全的方法倒是非常容易的：在构造函数中直接调用虚拟函数，就象调用其它函数一样。但是，这样就意味着，任何虚拟函数都无法编写了，因为它们需要依靠基类的固定的创建(invariantsestablished by base classes)。这将会导致一片混乱。<br><br><br><br>有没有&#8220;指定位置删除&#8221;(placement delete)？<br><br><br><br>没有，不过如果你需要的话，可以自己写一个。<br><br><br><br>看看这个指定位置创建(placement new)，它将对象放进了一系列Arena中；<br><br>
<div class=quote>
<div class=quote-title><u></u></div>
<div class=quote-content>class Arena {<br><br>public:<br><br>void* allocate(size_t);<br><br>void deallocate(void*);<br><br>// ...<br><br>};<br><br><br><br>void* operator new(size_t sz, Arena&amp; a)<br><br>{<br><br>return a.allocate(sz);<br><br>}<br><br><br><br>Arena a1(some arguments);<br><br>Arena a2(some arguments);<br><br></div>
</div>
这样实现了之后，我们就可以这么写：<br><br><br><br>
<div class=quote>
<div class=quote-title><u></u></div>
<div class=quote-content>X* p1 = new(a1) X;<br><br>Y* p2 = new(a1) Y;<br><br>Z* p3 = new(a2) Z;<br><br>// ...<br><br></div>
</div>
但是，以后怎样正确地销毁这些对象呢？没有对应于这种&#8220;placement new&#8221;的内建的&#8220;placement delete&#8221;，原因是，没有一种通用的方法可以保证它被正确地使用。在C++的类型系统中，没有什么东西可以让我们确认，p1一定指向一个由Arena类型的a1分派的对象。p1可能指向任何东西分派的任何一块地方。<br><br><br><br>然而，有时候程序员是知道的，所以这是一种方法：<br><br><br><br>
<div class=quote>
<div class=quote-title><u></u></div>
<div class=quote-content>template&lt;class T&gt; void destroy(T* p, Arena&amp; a)<br><br>{<br><br>if (p) {<br><br>p-&gt;~T(); // explicit destructor call<br><br>a.deallocate(p);<br><br>}<br><br>}</div>
</div>
<br><br>现在我们可以这么写：<br><br>
<div class=quote>
<div class=quote-title><u></u></div>
<div class=quote-content>destroy(p1,a1);<br><br>destroy(p2,a2);<br><br>destroy(p3,a3);</div>
</div>
<br><br>如果Arena维护了它保存着的对象的线索，你甚至可以自己写一个析构函数，以避免它发生错误。<br><br><br><br>这也是可能的：定义一对相互匹配的操作符new()和delete()，以维护《C++程序设计语言》15.6中的类继承体系。参见《C++语言的设计和演变》10.4和《C++程序设计语言》19.4.5。<br><br><br><br>我能防止别人继承我自己的类吗？<br><br><br><br>可以，但你为什么要那么做呢？这是两个常见的回答：<br><br><br><br>效率：避免我的函数被虚拟调用<br><br>安全：保证我的类不被用作一个基类（例如，保证我能够复制对象而不用担心出事）<br><br><br><br>根据我的经验，效率原因往往是不必要的担心。在C++中，虚拟函数调用是如此之快，以致于它们在一个包含虚拟函数的类中被实际使用时，相比普通的函数调用，根本不会产生值得考虑的运行期开支。注意，仅仅通过指针或引用时，才会使用虚拟调用机制。当直接通过对象名字调用一个函数时，虚拟函数调用的开支可以被很容易地优化掉。<br><br><br><br>如果确实有真正的需要，要将一个类封闭起来以防止虚拟调用，那么可能首先应该问问为什么它们是虚拟的。我看见过一些例子，那些性能表现不佳的函数被设置为虚拟，没有其他原因，仅仅是因为&#8220;我们习惯这么干&#8221;。<br><br><br><br>这个问题的另一个部分，由于逻辑上的原因如何防止类被继承，有一个解决方案。不幸的是，这个方案并不完美。它建立在这样一个事实的基础之上，那就是：大多数的继承类必须建立一个虚拟的基类。这是一个例子：<br><br>
<div class=quote>
<div class=quote-title><u></u></div>
<div class=quote-content>classUsable;<br><br><br><br>classUsable_lock {<br><br>friendclass Usable;<br><br>private:<br><br>Usable_lock(){}<br><br>Usable_lock(constUsable_lock&amp;) {}<br><br>};<br><br><br><br>classUsable : public virtual Usable_lock {<br><br>//...<br><br>public:<br><br>Usable();<br><br>Usable(char*);<br><br>//...<br><br>};<br><br><br><br>Usablea;<br><br><br><br>classDD : public Usable { };<br><br><br><br>DDdd; // 错误: DD::DD() 不能访问<br><br>// Usable_lock::Usable_lock()是一个私有成员<br><br></div>
</div>
(来自《C++语言的设计和演变》11.4.3)<br><br><br><br>为什么不能为模板参数定义约束（constraints）？<br><br><br><br>可以的，而且方法非常简单和通用。<br><br><br><br>看看这个：<br><br><br><br>
<div class=quote>
<div class=quote-title><u></u></div>
<div class=quote-content>template&lt;class Container&gt;<br><br>void draw_all(Container&amp; c)<br><br>{<br><br>for_each(c.begin(),c.end(),mem_fun(&amp;Shape::draw));<br><br>}</div>
</div>
<br><br>如果出现类型错误，可能是发生在相当复杂的for_each()调用时。例如，如果容器的元素类型是int，我们将得到一个和for_each()相关的含义模糊的错误(因为不能够对对一个int值调用Shape::draw的方法)。<br><br><br><br>为了提前捕捉这个错误，我这样写：<br><br><br><br>
<div class=quote>
<div class=quote-title><u></u></div>
<div class=quote-content>template&lt;class Container&gt;<br><br>void draw_all(Container&amp; c)<br><br>{<br><br>Shape* p = c.front(); // accept only containers of Shape*s<br><br><br><br>for_each(c.begin(),c.end(),mem_fun(&amp;Shape::draw));<br><br>}</div>
</div>
<br><br>对于现在的大多数编译器，中间变量p的初始化将会触发一个易于了解的错误。这个窍门在很多语言中都是通用的，而且在所有的标准创建中都必须这样做。在成品的代码中，我也许可以这样写：<br><br><br><br>
<div class=quote>
<div class=quote-title><u></u></div>
<div class=quote-content>template&lt;classContainer&gt;<br><br>void draw_all(Container&amp; c)<br><br>{<br><br>typedef typename Container::value_type T;<br><br>Can_copy&lt;T,Shape*&gt;(); // accept containers of only Shape*s<br><br><br><br>for_each(c.begin(),c.end(),mem_fun(&amp;Shape::draw));<br><br>}</div>
</div>
<br><br>这样就很清楚了，我在建立一个断言(assertion)。Can_copy模板可以这样定义：<br><br>
<div class=quote>
<div class=quote-title><u></u></div>
<div class=quote-content>template&lt;classT1, class T2&gt; struct Can_copy {<br><br>staticvoid constraints(T1 a, T2 b) { T2 c = a; b = a; }<br><br>Can_copy(){ void(*p)(T1,T2) = constraints; }<br><br>};</div>
</div>
<br><br>Can_copy(在运行时)检查T1是否可以被赋值给T2。Can_copy&lt;T,Shape*&gt;检查T是否是Shape*类型，或者是一个指向由Shape类公共继承而来的类的对象的指针，或者是被用户转换到Shape*类型的某个类型。注意这个定义被精简到了最小：<br><br><br><br>一行命名要检查的约束，和要检查的类型<br><br>一行列出指定的要检查的约束(constraints()函数)<br><br>一行提供触发检查的方法(通过构造函数)<br><br><br><br>注意这个定义有相当合理的性质：<br><br><br><br>你可以表达一个约束，而不用声明或复制变量，因此约束的编写者可以用不着去设想变量如何被初始化，对象是否能够被复制，被销毁，以及诸如此类的事情。(当然，约束要检查这些属性的情况时例外。)<br><br>使用现在的编译器，不需要为约束产生代码<br><br>定义和使用约束，不需要使用宏<br><br>当约束失败时，编译器会给出可接受的错误信息，包括&#8220;constraints&#8221;这个词（给用户一个线索），约束的名字，以及导致约束失败的详细错误（例如&#8220;无法用double*初始化Shape*&#8221;）。<br><br><br><br>那么，在C++语言中，有没有类似于Can_copy——或者更好——的东西呢？在《C++语言的设计和演变》中，对于在C++中实现这种通用约束的困难进行了分析。从那以来，出现了很多方法，来让约束类变得更加容易编写，同时仍然能触发良好的错误信息。例如，我信任我在Can_copy中使用的函数指针的方式，它源自Alex Stepanov和Jeremy Siek。我并不认为Can_copy()已经可以标准化了——它需要更多的使用。同样，在C++社区中，各种不同的约束方式被使用；到底是哪一种约束模板在广泛的使用中被证明是最有效的，还没有达成一致的意见。<br><br><br><br>但是，这种方式非常普遍，比语言提供的专门用于约束检查的机制更加普遍。无论如何，当我们编写一个模板时，我们拥有了C++提供的最丰富的表达力量。看看这个：<br><br>
<div class=quote>
<div class=quote-title><u></u></div>
<div class=quote-content>template&lt;classT, class B&gt; struct Derived_from {<br><br>staticvoid constraints(T* p) { B* pb = p; }<br><br>Derived_from(){ void(*p)(T*) = constraints; }<br><br>};</div>
</div>
<div class=quote>
<div class=quote-title><u></u></div>
<div class=quote-content>template&lt;classT1, class T2&gt; struct Can_copy {<br><br>staticvoid constraints(T1 a, T2 b) { T2 c = a; b = a; }<br><br>Can_copy(){ void(*p)(T1,T2) = constraints; }<br><br>};</div>
</div>
<div class=quote>
<div class=quote-title><u></u></div>
<div class=quote-content>template&lt;classT1, class T2 = T1&gt; struct Can_compare {<br><br>staticvoid constraints(T1 a, T2 b) { a==b; a!=b; a&lt;b; }<br><br>Can_compare(){ void(*p)(T1,T2) = constraints; }<br><br>};</div>
</div>
<div class=quote>
<div class=quote-title><u></u></div>
<div class=quote-content>template&lt;classT1, class T2, class T3 = T1&gt; struct Can_multiply {<br><br>staticvoid constraints(T1 a, T2 b, T3 c) { c = a*b; }<br><br>Can_multiply(){ void(*p)(T1,T2,T3) = constraints; }<br><br>};</div>
</div>
<div class=quote>
<div class=quote-title><u></u></div>
<div class=quote-content>structB { };<br><br>structD : B { };<br><br>structDD : D { };<br><br>structX { };<br><br><br><br>intmain()<br><br>{<br><br>Derived_from&lt;D,B&gt;();<br><br>Derived_from&lt;DD,B&gt;();<br><br>Derived_from&lt;X,B&gt;();<br><br>Derived_from&lt;int,B&gt;();<br><br>Derived_from&lt;X,int&gt;();<br><br><br><br>Can_compare&lt;int,float&gt;();<br><br>Can_compare&lt;X,B&gt;();<br><br>Can_multiply&lt;int,float&gt;();<br><br>Can_multiply&lt;int,float,double&gt;();<br><br>Can_multiply&lt;B,X&gt;();<br><br><br><br>Can_copy&lt;D*,B*&gt;();<br><br>Can_copy&lt;D,B*&gt;();<br><br>Can_copy&lt;int,B*&gt;();<br><br>}</div>
</div>
<br><br>//典型的&#8220;元素必须继承自Mybase*&#8221;约束:<br><br>
<div class=quote>
<div class=quote-title><u></u></div>
<div class=quote-content>template&lt;classT&gt; class Container : Derived_from&lt;T,Mybase&gt; {<br><br>//...<br><br>};</div>
</div>
<br><br>事实上，Derived_from并不检查来源（derivation），而仅仅检查转换（conversion），不过这往往是一个更好的约束。为约束想一个好名字是很难的。<br><br><br><br>既然已经有了优秀的qsort()函数，为什么还需要一个sort()？<br><br><br><br>对于初学者来说，<br><br>
<div class=quote>
<div class=quote-title><u></u></div>
<div class=quote-content>qsort(array,asize,sizeof(elem),elem_compare);</div>
</div>
<br><br>看上去太古怪了，而且比这个更难理解：<br><br>
<div class=quote>
<div class=quote-title><u></u></div>
<div class=quote-content>sort(vec.begin(),vec.end());</div>
</div>
<br><br>对于专家来说，在元素与比较方式（comparisoncriteria）都相同的情况下，sort()比qsort()更快，这是很重要的。而且，qsort()是通用的，所以它可以用于不同容器类型、元素类型、比较方式的任意有意义的组合。举例来说：<br><br>
<div class=quote>
<div class=quote-title><u></u></div>
<div class=quote-content>structRecord {<br><br>stringname;<br><br>//...<br><br>};<br><br><br><br>structname_compare { // 使用"name"作为键比较Record<br><br>booloperator()(const Record&amp; a, const Record&amp; b) const<br><br>{return a.name&lt;b.name; }<br><br>};<br><br><br><br>voidf(vector&lt;Record&gt;&amp; vs)<br><br>{<br><br>sort(vs.begin(),vs.end(), name_compare());<br><br>//...<br><br>} </div>
</div>
<br><br>而且，很多人欣赏sort()是因为它是类型安全的，使用它不需要进行造型（cast），没有人必须去为基本类型写一个compare()函数。<br><br><br><br>更多的细节，参见我的文章《将标准C++作为一种新的语言来学习》（Learning C++ as a Newlanguage），可以从我的文章列表中找到。<br><br><br><br>sort()胜过qsort()的主要原因是，比较操作在内联（inlines）上做得更好。<br><br><br><br>什么是函数对象（function object）？<br><br><br><br>顾名思义，就是在某种方式上表现得象一个函数的对象。典型地，它是指一个类的实例，这个类定义了应用操作符operator()。<br><br><br><br>函数对象是比函数更加通用的概念，因为函数对象可以定义跨越多次调用的可持久的部分（类似静态局部变量），同时又能够从对象的外面进行初始化和检查（和静态局部变量不同）。例如：<br><br>
<div class=quote>
<div class=quote-title><u></u></div>
<div class=quote-content>classSum {<br><br>intval;<br><br>public:<br><br>Sum(inti) :val(i) { }<br><br>operatorint() const { return val; } // 取得值<br><br><br><br>intoperator()(int i) { return val+=i; } // 应用<br><br>};<br><br><br><br>voidf(vector v)<br><br>{<br><br>Sums = 0; // initial value 0<br><br>s= for_each(v.begin(), v.end(), s); // 求所有元素的和<br><br>cout&lt;&lt; "the sum is " &lt;&lt; s &lt;&lt; "\n";<br><br><br><br>//或者甚至：<br><br>cout&lt;&lt; "the sum is " &lt;&lt; for_each(v.begin(), v.end(), Sum(0))&lt;&lt; "\n";<br><br>}</div>
</div>
<br><br>注意一个拥有应用操作符的函数对象可以被完美地内联化（inline），因为它没有涉及到任何指针，后者可能导致拒绝优化。与之形成对比的是，现有的优化器几乎不能（或者完全不能？）将一个通过函数指针的调用内联化。<br><br><br><br>在标准库中，函数对象被广泛地使用以获得弹性。<br><br><br><br>我应该如何对付内存泄漏？<br><br><br><br>写出那些不会导致任何内存泄漏的代码。很明显，当你的代码中到处充满了new 操作、delete操作和指针运算的话，你将会在某个地方搞晕了头，导致内存泄漏，指针引用错误，以及诸如此类的问题。这和你如何小心地对待内存分配工作其实完全没有关系：代码的复杂性最终总是会超过你能够付出的时间和努力。于是随后产生了一些成功的技巧，它们依赖于将内存分配（allocations）与重新分配（deallocation）工作隐藏在易于管理的类型之后。标准容器（standardcontainers）是一个优秀的例子。它们不是通过你而是自己为元素管理内存，从而避免了产生糟糕的结果。想象一下，没有string和vector的帮助，写出这个：<br><br>
<div class=quote>
<div class=quote-title><u></u></div>
<div class=quote-content>#include&lt;vector&gt;<br><br>#include&lt;string&gt;<br><br>#include&lt;iostream&gt;<br><br>#include&lt;algorithm&gt;<br><br>usingnamespace std;<br><br><br><br>intmain() // small program messing around with strings<br><br>{<br><br>cout&lt;&lt; "enter some whitespace-separated words:\n";<br><br>vector&lt;string&gt;v;<br><br>strings;<br><br>while(cin&gt;&gt;s) v.push_back(s);<br><br><br><br>sort(v.begin(),v.end());<br><br><br><br>stringcat;<br><br>typedefvector&lt;string&gt;::const_iterator Iter;<br><br>for(Iter p = v.begin(); p!=v.end(); ++p) cat += *p+"+";<br><br>cout&lt;&lt; cat &lt;&lt; '\n';<br><br>}</div>
</div>
<br><br>你有多少机会在第一次就得到正确的结果？你又怎么知道你没有导致内存泄漏呢？<br><br><br><br>注意，没有出现显式的内存管理，宏，造型，溢出检查，显式的长度限制，以及指针。通过使用函数对象和标准算法（standard algorithm），我可以避免使用指针——例如使用迭代子（iterator），不过对于一个这么小的程序来说有点小题大作了。<br><br><br><br>这些技巧并不完美，要系统化地使用它们也并不总是那么容易。但是，应用它们产生了惊人的差异，而且通过减少显式的内存分配与重新分配的次数，你甚至可以使余下的例子更加容易被跟踪。早在1981年，我就指出，通过将我必须显式地跟踪的对象的数量从几万个减少到几打，为了使程序正确运行而付出的努力从可怕的苦工，变成了应付一些可管理的对象，甚至更加简单了。<br><br><br><br>如果你的程序还没有包含将显式内存管理减少到最小限度的库，那么要让你程序完成和正确运行的话，最快的途径也许就是先建立一个这样的库。<br><br><br><br>模板和标准库实现了容器、资源句柄以及诸如此类的东西，更早的使用甚至在多年以前。异常的使用使之更加完善。<br><br><br><br>如果你实在不能将内存分配/重新分配的操作隐藏到你需要的对象中时，你可以使用资源句柄（resource handle），以将内存泄漏的可能性降至最低。这里有个例子：我需要通过一个函数，在空闲内存中建立一个对象并返回它。这时候可能忘记释放这个对象。毕竟，我们不能说，仅仅关注当这个指针要被释放的时候，谁将负责去做。使用资源句柄，这里用了标准库中的auto_ptr，使需要为之负责的地方变得明确了。<br><br>
<div class=quote>
<div class=quote-title><u></u></div>
<div class=quote-content>#include&lt;memory&gt;<br><br>#include&lt;iostream&gt;<br><br>usingnamespace std;<br><br><br><br>structS {<br><br>S(){ cout &lt;&lt; "make an S\n"; }<br><br>~S(){ cout &lt;&lt; "destroy an S\n"; }<br><br>S(constS&amp;) { cout &lt;&lt; "copy initialize an S\n"; }<br><br>S&amp;operator=(const S&amp;) { cout &lt;&lt; "copy assign an S\n"; }<br><br>};<br><br><br><br>S*f()<br><br>{<br><br>returnnew S; // 谁该负责释放这个S？<br><br>};<br><br><br><br>auto_ptr&lt;S&gt;g()<br><br>{<br><br>returnauto_ptr&lt;S&gt;(new S); // 显式传递负责释放这个S<br><br>}<br><br><br><br>intmain()<br><br>{<br><br>cout&lt;&lt; "start main\n";<br><br>S*p = f();<br><br>cout&lt;&lt; "after f() before g()\n";<br><br>// S*q = g(); // 将被编译器捕捉<br><br>auto_ptr&lt;S&gt;q = g();<br><br>cout&lt;&lt; "exit main\n";<br><br>//*p产生了内存泄漏<br><br>//*q被自动释放<br><br>}</div>
</div>
<br><br>在更一般的意义上考虑资源，而不仅仅是内存。<br><br><br><br>如果在你的环境中不能系统地应用这些技巧（例如，你必须使用别的地方的代码，或者你的程序的另一部分简直是原始人类（译注：原文是Neanderthals，尼安德特人，旧石器时代广泛分布在欧洲的猿人）写的，如此等等），那么注意使用一个内存泄漏检测器作为开发过程的一部分，或者插入一个垃圾收集器（garbage collector）。<br><br><br><br>我为什么在捕获一个异常之后就不能继续？<br><br><br><br>换句话说，C++为什么不提供一种简单的方式，让程序能够回到异常抛出点之后，并继续执行？<br><br><br><br>主要的原因是，如果从异常处理之后继续，那么无法预知掷出点之后的代码如何对待异常处理，是否仅仅继续执行，就象什么也没有发生一样。异常处理者无法知道，在继续之前，有关的上下文环境（context）是否是&#8220;正确&#8221;的。要让这样的代码正确执行，抛出异常的编写者与捕获异常的编写者必须对彼此的代码与上下文环境都非常熟悉才行。这样会产生非常复杂的依赖性，因此无论在什么情况下，都会导致一系列严重的维护问题。<br><br><br><br>当我设计C++的异常处理机制时，我曾经认真地考虑过允许这种继续的可能性，而且在标准化的过程中，这个问题被非常详细地讨论过。请参见《C++语言的设计和演变》中的异常处理章节。<br><br><br><br>在一次新闻组的讨论中，我曾经以一种稍微不同的方式回答过这个问题。<br><br><br><br>为什么C++中没有相当于realloc()的函数？<br><br><br><br>如果你需要，你当然可以使用realloc()。但是，realloc()仅仅保证能工作于这样的数组之上：它们被malloc()（或者类似的函数）分配，包含一些没有用户定义的复制构造函数（copy constructors）的对象。而且，要记住，与通常的期望相反，realloc()有时也必须复制它的参数数组。<br><br><br><br>在C++中，处理内存重新分配的更好的方法是，使用标准库中的容器，例如vector，并让它自我增长。<br><br><br><br>如何使用异常？<br><br><br><br>参见《C++程序设计语言》第4章，第8.3节，以及附录E。这个附录针对的是如何在要求苛刻的程序中写出异常安全的代码的技巧，而不是针对初学者的。一个关键的技术是&#8220;资源获得即初始化&#8221;（resourceacquisiton is initialization），它使用一些有析构函数的类，来实现强制的资源管理。<br><br><br><br>怎样从输入中读取一个字符串？<br><br><br><br>你可以用这种方式读取一个单独的以空格结束的词：<br><br><br><br>
<div class=quote>
<div class=quote-title><u></u></div>
<div class=quote-content>#include&lt;iostream&gt;<br><br>#include&lt;string&gt;<br><br>usingnamespace std;<br><br><br><br>intmain()<br><br>{<br><br>cout&lt;&lt; "Please enter a word:\n";<br><br><br><br>strings;<br><br>cin&gt;&gt;s;<br><br><br><br>cout&lt;&lt; "You entered " &lt;&lt; s &lt;&lt; '\n';<br><br>}</div>
</div>
<br><br>注意，这里没有显式的内存管理，也没有可能导致溢出的固定大小的缓冲区。<br><br><br><br>如果你确实想得到一行而不是一个单独的词，可以这样做：<br><br><br><br>
<div class=quote>
<div class=quote-title><u></u></div>
<div class=quote-content>#include&lt;iostream&gt;<br><br>#include&lt;string&gt;<br><br>usingnamespace std;<br><br><br><br>intmain()<br><br>{<br><br>cout&lt;&lt; "Please enter a line:\n";<br><br><br><br>strings;<br><br>getline(cin,s);<br><br><br><br>cout&lt;&lt; "You entered " &lt;&lt; s &lt;&lt; '\n';<br><br>}</div>
</div>
<br><br>在《C++程序设计语言》（可在线获得）的第3章，可以找到一个对诸如字符串与流这样的标准库工具的简介。对于使用C与C++进行简单输入输出的详细比较，参见我的文章《将标准C++作为一种新的语言来学习》(Learning Standard C++ as aNew Language)，你可以在本人著作列表(my publications list)中下载到它。<br><br><br><br>为什么C++不提供&#8220;finally&#8221;的构造？<br><br><br><br>因为C++提供了另外一种方法，它几乎总是更好的：&#8220;资源获得即初始化&#8221;（resourceacquisiton is initialization）技术。基本的思路是，通过一个局部对象来表现资源，于是局部对象的析构函数将会释放资源。这样，程序员就不会忘记释放资源了。举例来说：<br><br>
<div class=quote>
<div class=quote-title><u></u></div>
<div class=quote-content>classFile_handle {<br><br>FILE*p;<br><br>public:<br><br>File_handle(constchar* n, const char* a)<br><br>{p = fopen(n,a); if (p==0) throw Open_error(errno); }<br><br>File_handle(FILE*pp)<br><br>{p = pp; if (p==0) throw Open_error(errno); }<br><br><br><br>~File_handle(){ fclose(p); }<br><br><br><br>operatorFILE*() { return p; }<br><br><br><br>//...<br><br>};<br><br><br><br>voidf(const char* fn)<br><br>{<br><br>File_handlef(fn,"rw"); //打开fn进行读写<br><br>//通过f使用文件<br><br>}</div>
</div>
<br><br>在一个系统中，需要为每一个资源都使用一个&#8220;资源句柄&#8221;类。无论如何，我们不需要为每一个资源获得都写出&#8220;finally&#8221;语句。在实时系统中，资源获得要远远多于资源的种类，因此和使用&#8220;finally&#8221;构造相比，&#8220;资源获得即初始化&#8221;技术会产生少得多的代码。<br><br><br><br>什么是自动指针（auto_ptr），为什么没有自动数组（auto_array）？<br><br><br><br>auto_ptr是一个非常简单的句柄类的例子，在&lt;memory&gt;中定义，通过&#8220;资源获得即初始化&#8221;技术支持异常安全。auto_ptr保存着一个指针，能够象指针一样被使用，并在生存期结束时释放指向的对象。举例：<br><br>
<div class=quote>
<div class=quote-title><u></u></div>
<div class=quote-content>#include&lt;memory&gt;<br><br>usingnamespace std;<br><br><br><br>structX {<br><br>intm;<br><br>//..<br><br>};<br><br><br><br>voidf()<br><br>{<br><br>auto_ptr&lt;X&gt;p(new X);<br><br>X*q = new X;<br><br><br><br>p-&gt;m++; //象一个指针一样使用p<br><br>q-&gt;m++;<br><br>//...<br><br><br><br>deleteq;<br><br>}</div>
</div>
<br><br>如果在...部分抛出了一个异常，p持有的对象将被auto_ptr的析构函数正确地释放，而q指向的X对象则产生了内存泄漏。更多的细节，参见《C++程序设计语言》14.4.2节。<br><br><br><br>auto_ptr是一个非常简单的类。特别地，它不是一个引用计数（reference counted）的指针。如果你将一个auto_ptr赋值给另一个，那么被赋值的auto_ptr将持有指针，而原来的auto_ptr将持有0。举例：<br><br><br><br>
<div class=quote>
<div class=quote-title><u></u></div>
<div class=quote-content>#include&lt;memory&gt;<br><br>#include&lt;iostream&gt;<br><br>usingnamespace std;<br><br><br><br>structX {<br><br>intm;<br><br>//..<br><br>};<br><br><br><br>intmain()<br><br>{<br><br>auto_ptr&lt;X&gt;p(new X);<br><br>auto_ptr&lt;X&gt;q(p);<br><br>cout&lt;&lt; "p " &lt;&lt; p.get() &lt;&lt; " q " &lt;&lt;q.get() &lt;&lt; "\n";<br><br>}</div>
</div>
<br><br>将会打印出一个指向0的指针和一个指向非0的指针。例如：<br><br>
<div class=quote>
<div class=quote-title><u></u></div>
<div class=quote-content>p0x0 q 0x378d0</div>
</div>
<br><br>auto_ptr::get()返回那个辅助的指针。<br><br><br><br>这种&#8220;转移&#8221;语义不同于通常的&#8220;复制&#8221;语义，这是令人惊讶的。特别地，永远不要使用auto_ptr作为一个标准容器的成员。标准容器需要通常的&#8220;复制&#8221;语义。例如：<br><br><br><br>
<div class=quote>
<div class=quote-title><u></u></div>
<div class=quote-content>std::vector&lt;auto_ptr&lt;X&gt;&gt;v; // 错误</div>
</div>
<br><br>auto_ptr只持有指向一个单独元素的指针，而不是指向一个数组的指针：<br><br><br><br>
<div class=quote>
<div class=quote-title><u></u></div>
<div class=quote-content>voidf(int n)<br><br>{<br><br>auto_ptr&lt;X&gt;p(new X[n]); //错误<br><br>//...<br><br>}</div>
</div>
<br><br>这是错误的，因为析构函数会调用delete而不是delete[]来释放指针，这样就不会调用余下的n-1个X的析构函数。<br><br><br><br>那么我们需要一个auto_array来持有数组吗？不。没有auto_array。原因是根本没有这种需要。更好的解决方案是使用vector：<br><br><br><br>
<div class=quote>
<div class=quote-title><u></u></div>
<div class=quote-content>voidf(int n)<br><br>{<br><br>vector&lt;X&gt;v(n);<br><br>//...<br><br>}</div>
</div>
<br><br>当...部分发生异常时，v的析构函数会被正确地调用。<br><br><br><br>可以混合使用C风格与C++风格的内存分派与重新分配吗？<br><br><br><br>在这种意义上是可以的：你可以在同一个程序中使用malloc()和new。<br><br><br><br>在这种意义上是不行的：你不能使用malloc()来建立一个对象，又通过delete来释放它。你也不能用new建立一个新的对象，然后通过free()来释放它，或者通过realloc()在数组中再建立一个新的。<br><br><br><br>C++中的new和delete操作可以保证正确的构造和析构：构造函数和析构函数在需要它们的时候被调用。C风格的函数alloc(), calloc(), free(), 和realloc()却不能保证这一点。此外，用new和delete来获得和释放的原始内存，并不一定能保证与malloc()和free()兼容。如果这种混合的风格在你的系统中能够运用，只能说是你走运——暂时的。<br><br><br><br>如果你觉得需要使用realloc()——或者要做更多——考虑使用标准库中的vector。例如：<br><br><br><br>//从输入中将词读取到一个字符串vector中<br><br>
<div class=quote>
<div class=quote-title><u></u></div>
<div class=quote-content>vector&lt;string&gt;words;<br><br>strings;<br><br>while(cin&gt;&gt;s &amp;&amp; s!=".") words.push_back(s);</div>
</div>
<br><br>vector会视需要自动增长。<br><br><br><br>更多的例子与讨论，参见我的文章《将标准C++作为一种新的语言来学习》(Learning Standard C++ as aNew Language)，你可以在本人著作列表(my publications list)中下载到它。<br><br><br><br>我为什么必须使用一个造型来转换*void？<br><br><br><br>在C语言中，你可以隐式地将*void转换为*T。这是不安全的。考虑一下：<br><br><br><br>
<div class=quote>
<div class=quote-title><u></u></div>
<div class=quote-content>#include&lt;stdio.h&gt;<br><br><br><br>intmain()<br><br>{<br><br>chari = 0;<br><br>charj = 0;<br><br>char*p = &amp;i;<br><br>void*q = p;<br><br>int*pp = q; /* 不安全的，在C中可以，C++不行 */<br><br><br><br>printf("%d%d\n",i,j);<br><br>*pp= -1; /* 覆盖了从i开始的内存 */<br><br>printf("%d%d\n",i,j);<br><br>}</div>
</div>
<br><br>使用一个并不指向T类型的T*将是一场灾难。因此，在C++中，如果从一个void*得到一个T*，你必须进行显式转换。举例来说，要得到上列程序的这个令人别扭的效果，你可以这样写：<br><br>
<div class=quote>
<div class=quote-title><u></u></div>
<div class=quote-content>int*pp = (int*)q;</div>
</div>
<br><br>或者使用一个新的类型造型，以使这种没有检查的类型转换操作变得更加清晰：<br><br>
<div class=quote>
<div class=quote-title><u></u></div>
<div class=quote-content>int*pp = static_cast&lt;int*&gt;(q);</div>
</div>
<br><br>造型被最好地避免了。<br><br><br><br>在C语言中，这种不安全的转换最常见的应用之一，是将malloc()的结果赋予一个合适的指针。例如：<br><br>
<div class=quote>
<div class=quote-title><u></u></div>
<div class=quote-content>int*p = malloc(sizeof(int));</div>
</div>
<br><br>在C++中，使用类型安全的new操作符：<br><br><br><br>
<div class=quote>
<div class=quote-title><u></u></div>
<div class=quote-content>int*p = new int;</div>
</div>
<br><br>附带地，new操作符还提供了胜过malloc()的新特性：<br><br><br><br>new不会偶然分配错误的内存数量；<br><br>new会隐式地检查内存耗尽情况，而且<br><br>new提供了初始化。<br><br><br><br>举例：
<div class=quote>
<div class=quote-title><u></u></div>
<div class=quote-content><br><br>typedefstd::complex&lt;double&gt; cmplx;<br><br><br><br>/*C风格: */<br><br>cmplx*p = (cmplx*)malloc(sizeof(int)); /* 错误：类型不正确 */<br><br>/*忘记测试p==0 */<br><br>if(*p == 7) { /* ... */ } /*糟糕，忘记了初始化*p */<br><br><br><br>//C++风格:<br><br>cmplx*q = new cmplx(1,2); // 如果内存耗尽，将抛出一个bad_alloc异常<br><br>if(*q == 7) { /* ... */ }</div>
</div>
<br><br>我如何定义一个类内部（in-class）的常量？<br><br><br><br>如果你需要一个通过常量表达式来定义的常量，例如数组的范围，你有两种选择：
<div class=quote>
<div class=quote-title><u></u></div>
<div class=quote-content><br><br>classX {<br><br>staticconst int c1 = 7;<br><br>enum{ c2 = 19 };<br><br><br><br>charv1[c1];<br><br>charv2[c2];<br><br><br><br>//...<br><br>};<br><br></div>
</div>
乍看起来，c1的声明要更加清晰，但是要注意的是，使用这种类内部的初始化语法的时候，常量必须是被一个常量表达式初始化的整型或枚举类型，而且必须是static和const形式。这是很严重的限制：
<div class=quote>
<div class=quote-title><u></u></div>
<div class=quote-content><br><br>classY {<br><br>constint c3 = 7; // 错误：不是static<br><br>staticint c4 = 7; // 错误：不是const<br><br>staticconst float c5 = 7; // 错误：不是整型<br><br>};</div>
</div>
<br><br>我倾向使用枚举的方式，因为它更加方便，而且不会诱使我去使用不规范的类内初始化语法。<br><br><br><br>那么，为什么会存在这种不方便的限制呢？一般来说，类在一个头文件中被声明，而头文件被包含到许多互相调用的单元去。但是，为了避免复杂的编译器规则，C++要求每一个对象只有一个单独的定义。如果C++允许在类内部定义一个和对象一样占据内存的实体的话，这种规则就被破坏了。对于C++在这个设计上的权衡，请参见《C++语言的设计和演变》。<br><br><br><br>如果你不需要用常量表达式来初始化它，那么可以获得更大的弹性：<br><br>
<div class=quote>
<div class=quote-title><u></u></div>
<div class=quote-content>classZ {<br><br>staticchar* p; // 在定义中初始化<br><br>constint i; // 在构造函数中初始化<br><br>public:<br><br>Z(intii) :i(ii) { }<br><br>};<br><br><br><br>char*Z::p = "hello, there";<br><br></div>
</div>
你可以获取一个static成员的地址，当且仅当它有一个类外部的定义的时候：<br><br>
<div class=quote>
<div class=quote-title><u></u></div>
<div class=quote-content>classAE {<br><br>//...<br><br>public:<br><br>staticconst int c6 = 7;<br><br>staticconst int c7 = 31;<br><br>};<br><br><br><br>constint AE::c7; // 定义<br><br><br><br>intf()<br><br>{<br><br>constint* p1 = &amp;AE::c6; // 错误：c6没有左值<br><br>constint* p2 = &amp;AE::c7; // ok<br><br>//...<br><br>}</div>
</div>
<br><br>为什么delete不会将操作数置0？<br><br><br><br>考虑一下：
<div class=quote>
<div class=quote-title><u></u></div>
<div class=quote-content><br><br>deletep;<br><br>//...<br><br>deletep;<br><br></div>
</div>
如果在...部分没有涉及到p的话，那么第二个&#8220;delete p;&#8221;将是一个严重的错误，因为C++的实现（译注：原文为a C++ implementation，当指VC++这样的实现了C++标准的具体工具）不能有效地防止这一点（除非通过非正式的预防手段）。既然delete 0从定义上来说是无害的，那么一个简单的解决方案就是，不管在什么地方执行了&#8220;delete p;&#8221;，随后都执行&#8220;p=0;&#8221;。但是，C++并不能保证这一点。<br><br><br><br>一个原因是，delete的操作数并不需要一个左值（lvalue）。考虑一下：<br><br>
<div class=quote>
<div class=quote-title><u></u></div>
<div class=quote-content>deletep+1;<br><br>deletef(x);<br><br></div>
</div>
在这里，被执行的delete并没有拥有一个可以被赋予0的指针。这些例子可能很少见，但它们的确指出了，为什么保证&#8220;任何指向被删除对象的指针都为0&#8221;是不可能的。绕过这条&#8220;规则&#8221;的一个简单的方法是，有两个指针指向同一个对象：<br><br>
<div class=quote>
<div class=quote-title><u></u></div>
<div class=quote-content>T*p = new T;<br><br>T*q = p;<br><br>deletep;<br><br>deleteq; // 糟糕！<br><br></div>
</div>
C++显式地允许delete操作将操作数左值置0，而且我曾经希望C++的实现能够做到这一点，但这种思想看来并没有在C++的实现中变得流行。<br><br><br><br>如果你认为指针置0很重要，考虑使用一个销毁的函数：<br><br>
<div class=quote>
<div class=quote-title><u></u></div>
<div class=quote-content>template&lt;classT&gt; inline void destroy(T*&amp; p) { delete p; p = 0; }</div>
</div>
<br><br>考虑一下，这也是为什么需要依靠标准库的容器、句柄等等，来将对new和delete的显式调用降到最低限度的另一个原因。<br><br><br><br>注意，通过引用来传递指针（以允许指针被置0）有一个额外的好处，能防止destroy()在右值上（rvalue）被调用：<br><br>
<div class=quote>
<div class=quote-title><u></u></div>
<div class=quote-content>int*f();<br><br>int*p;<br><br>//...<br><br>destroy(f()); //错误：应该使用一个非常量（non-const）的引用传递右值<br><br>destroy(p+1); //错误：应该使用一个非常量（non-const）的引用传递右值<br><br></div>
</div>
我能够写&#8220;void main()&#8221;吗？<br><br><br><br>这种定义：<br><br><br><br>
<div class=quote>
<div class=quote-title><u></u></div>
<div class=quote-content>voidmain() { /* ... */ }</div>
</div>
<br><br>在C++中从未被允许，在C语言中也是一样。参见ISO C++标准3.6.1[2]或者ISO C标准5.1.2.2.1。规范的实现接受这种方式：<br><br><br><br>
<div class=quote>
<div class=quote-title><u></u></div>
<div class=quote-content>intmain() { /* ... */ }</div>
</div>
<br><br>和<br><br><br><br>
<div class=quote>
<div class=quote-title><u></u></div>
<div class=quote-content>intmain(int argc, char* argv[]) { /* ... */ }</div>
</div>
<br><br>一个规范的实现可能提供许多版本的main()，但它们都必须返回int类型。main()返回的int值，是程序返回一个值给调用它的系统的方式。在那些不具备这种方式的系统中，返回值被忽略了，但这并不使&#8220;void main()&#8221;在C++或C中成为合法的。即使你的编译器接受了&#8220;void main()&#8221;，也要避免使用它，否则你将冒着被C和C++程序员视为无知的风险。<br><br><br><br>在C++中，main()并不需要包含显式的return语句。在这种情况下，返回值是0，表示执行成功。例如：<br><br>
<div class=quote>
<div class=quote-title><u></u></div>
<div class=quote-content>#include&lt;iostream&gt;<br><br><br><br>intmain()<br><br>{<br><br>std::cout&lt;&lt; "This program returns the integer value 0\n";<br><br>}<br><br></div>
</div>
注意，无论是ISO C++还是C99，都不允许在声明中漏掉类型。那就是说，与C89和ARM C++形成对照，当声明中缺少类型时，并不会保证是&#8220;int&#8221;。于是：
<div class=quote>
<div class=quote-title><u></u></div>
<div class=quote-content><br><br>#include&lt;iostream&gt;<br><br><br><br>main(){ /* ... */ }</div>
</div>
<br><br>是错误的，因为缺少main()的返回类型。<br><br><br><br>为什么我不能重载点符号，::，sizeof，等等？<br><br><br><br>大多数的运算符能够被程序员重载。例外的是：<br><br>
<div class=quote>
<div class=quote-title><u></u></div>
<div class=quote-content>.(点符号) :: ?: sizeof</div>
</div>
<br><br>并没有什么根本的原因要禁止重载?:。仅仅是因为，我没有发现有哪种特殊的情况需要重载一个三元运算符。注意一个重载了 表达式1？表达式2：表达式3 的函数，不能够保证表达式2：表达式3中只有一个会被执行。<br><br><br><br>Sizeof不能够被重载是因为内建的操作（built-in operations），诸如对一个指向数组的指针进行增量操作，必须依靠它。考虑一下：
<div class=quote>
<div class=quote-title><u></u></div>
<div class=quote-content><br><br>Xa[10];<br><br>X*p = &amp;a[3];<br><br>X*q = &amp;a[3];<br><br>p++; //p指向a[4]<br><br>//那么p的整型值必须比q的整型值大出一个sizeof(X)</div>
</div>
<br><br>所以，sizeof(X)不能由程序员来赋予一个不同的新意义，以免违反基本的语法。<br><br><br><br>在N::m中，无论N还是m都不是值的表达式；N和m是编译器知道的名字，::执行一个（编译期的）范围解析，而不是表达式求值。你可以想象一下，允许重载x::y的话，x可能是一个对象而不是一个名字空间（namespace）或者一个类，这样就会导致——与原来的表现相反——产生新的语法（允许 表达式1::表达式2）。很明显，这种复杂性不会带来任何好处。<br><br><br><br>理论上来说，.（点运算符）可以通过使用和-&gt;一样的技术来进行重载。但是，这样做会导致一个问题，那就是无法确定操作的是重载了.的对象呢，还是通过.引用的一个对象。例如：<br><br><br><br>
<div class=quote>
<div class=quote-title><u></u></div>
<div class=quote-content>classY {<br><br>public:<br><br>voidf();<br><br>//...<br><br>};<br><br><br><br>classX { // 假设你能重载.<br><br>Y*p;<br><br>Y&amp;operator.() { return *p; }<br><br>voidf();<br><br>//...<br><br>};<br><br><br><br>voidg(X&amp; x)<br><br>{<br><br>x.f(); //X::f还是Y::f还是错误？<br><br>}</div>
</div>
<br><br>这个问题能够用几种不同的方法解决。在标准化的时候，哪种方法最好还没有定论。更多的细节，请参见《C++语言的设计和演变》。<br><br><br><br>怎样将一个整型值转换为一个字符串？<br><br><br><br>最简单的方法是使用一个字符串流（stringstream）：<br><br>
<div class=quote>
<div class=quote-title><u></u></div>
<div class=quote-content>#include&lt;iostream&gt;<br><br>#include&lt;string&gt;<br><br>#include&lt;sstream&gt;<br><br>usingnamespace std;<br><br><br><br>stringitos(int i) // 将int转换成string<br><br>{<br><br>stringstreams;<br><br>s&lt;&lt; i;<br><br>returns.str();<br><br>}<br><br><br><br>intmain()<br><br>{<br><br>inti = 127;<br><br>stringss = itos(i);<br><br>constchar* p = ss.c_str();<br><br><br><br>cout&lt;&lt; ss &lt;&lt; " " &lt;&lt; p &lt;&lt; "\n";<br><br>}<br><br></div>
</div>
自然地，这种技术能够将任何使用&lt;&lt;输出的类型转换为字符串。对于字符串流的更多说明，参见《C++程序设计语言》21.5.3节。<br><br><br><br>&#8220;int* p&#8221;正确还是&#8220;int *p&#8221;正确？<br><br><br><br>二者都是正确的，因为二者在C和C++中都是有效的，而且意义完全一样。就语言的定义与相关的编译器来说，我们还可以说&#8220;int*p&#8221;或者&#8220;int * p&#8221;。<br><br><br><br>在&#8220;int* p&#8221;和&#8220;int *p&#8221;之间的选择与正确或错误无关，而只关乎风格与侧重点。C侧重表达式；对声明往往比可能带来的问题考虑得更多。另一方面，C++则非常重视类型。<br><br><br><br>一个&#8220;典型的C程序员&#8221;写成&#8220;int *p&#8221;，并且解释说&#8220;*p表示一个什么样的int&#8221;以强调语法，而且可能指出C（与C++）的语法来证明这种风格的正确性。是的，在语法上*被绑定到名字p上。<br><br><br><br>一个&#8220;典型的C++程序员&#8221;写成&#8220;int* p&#8221;，并且解释说&#8220;p是一个指向int的指针类型&#8221;以强调类型。是的，p是一个指向int的指针类型。我明确地倾向于这种侧重方向，而且认为对于学好更多的高级C++这是很重要的。<br><br><br><br>严重的混乱（仅仅）发生在当人们试图在一条声明中声明几个指针的时候：<br><br>
<div class=quote>
<div class=quote-title><u></u></div>
<div class=quote-content>int*p, p1; // 也许是错的：p1不是一个int*</div>
</div>
<br><br>把*放到名字这一边，看来也不能有效地减少这种错误：<br><br><br><br>
<div class=quote>
<div class=quote-title><u></u></div>
<div class=quote-content>int*p, p1; // 也许是错的？</div>
</div>
<br><br>为每一个名字写一条声明最大程度地解决了问题——特别是当我们初始化变量的时候。人们几乎不会这样写：
<div class=quote>
<div class=quote-title><u></u></div>
<div class=quote-content><br><br>int*p = &amp;i;<br><br>intp1 = p; // 错误：int用一个int*初始化了</div>
</div>
<br><br>如果他们真的这么干了，编译器也会指出。<br><br><br><br>每当事情可以有两种方法完成，有人就会迷惑。每当事情仅仅是一个风格的问题，争论就会没完没了。为每一个指针写一条声明，而且永远都要初始化变量，这样，混乱之源就消失了。更多的关于C的声明语法的讨论，参见《C++语言的设计和演变》。<br><br><br><br>对于我的代码，哪一种布局风格（layout style）是最好的？<br><br><br><br>这种风格问题属于个人的爱好。人们往往对布局风格的问题持有强烈的意见，不过，也许一贯性比某种特定的风格更加重要。象大多数人一样，我花了很长的时间，来为我的偏好作出一个固定的结论。<br><br><br><br>我个人使用通常称为&#8220;K&amp;R&#8221;的风格。当使用C语言没有的构造函数时，需要增加新的习惯，这样就变成了一种有时被称为&#8220;Stroustrup&#8221;的风格。例如：
<div class=quote>
<div class=quote-title><u></u></div>
<div class=quote-content><br><br>classC : public B {<br><br>public:<br><br>//...<br><br>};<br><br><br><br>voidf(int* p, int max)<br><br>{<br><br>if(p) {<br><br>//...<br><br>}<br><br><br><br>for(int i = 0; i&lt;max; ++i) {<br><br>//...<br><br>}<br><br>}</div>
</div>
<br><br>比大多数布局风格更好，这种风格保留了垂直的空格，我喜欢尽可能地在合理的情况下对齐屏幕。对函数开头的大括弧的放置，有助于我第一眼就分别出类的定义和函数的定义。<br><br><br><br>缩进是非常重要的。<br><br><br><br>设计问题，诸如作为主要接口的抽象基类的使用，使用模板以表现有弹性的类型安全的抽象，以及正确地使用异常以表现错误，比布局风格的选择要重要得多。<br><br><br><br>我应该将&#8220;const&#8221;放在类型之前还是之后？<br><br><br><br>我把它放在前面，但那仅仅是个人爱好问题。&#8220;const T&#8221;和&#8220;T const&#8221;总是都被允许的，而且是等效的。例如：<br><br>
<div class=quote>
<div class=quote-title><u></u></div>
<div class=quote-content>constint a = 1; // ok<br><br>intconst b = 2; // also ok<br><br></div>
</div>
我猜想第一种版本可能会让少数（更加固守语法规范）的程序员感到迷惑。<br><br><br><br>为什么？当我发明&#8220;const&#8221;（最初的名称叫做&#8220;readonly&#8221;，并且有一个对应的&#8220;writeonly&#8221;）的时候，我就允许它出现在类型之前或之后，因为这样做不会带来任何不明确。标准之前的C和C++规定了很少的（如果有的话）特定的顺序规范。<br><br><br><br>我不记得当时有过任何有关顺序问题的深入思考或讨论。那时，早期的一些使用者——特别是我——仅仅喜欢这种样子：<br><br><br><br>
<div class=quote>
<div class=quote-title><u></u></div>
<div class=quote-content>constint c = 10;</div>
</div>
<br><br>看起来比这种更好：<br><br>
<div class=quote>
<div class=quote-title><u></u></div>
<div class=quote-content>intconst c = 10;</div>
</div>
<br><br>也许我也受了这种影响：在我最早的一些使用&#8220;readonly&#8221;的例子中<br><br>
<div class=quote>
<div class=quote-title><u></u></div>
<div class=quote-content>readonlyint c = 10;</div>
</div>
<br><br>比这个更具有可读性：<br><br>
<div class=quote>
<div class=quote-title><u></u></div>
<div class=quote-content>intreadonly c = 10;</div>
</div>
<br><br>我创造的那些最早的使用&#8220;const&#8221;的（C或C++）代码，看来已经在全球范围内取代了&#8220;readonly&#8221;。<br><br><br><br>我记得这个语法的选择在几个人——例如Dennis Ritchie——当中讨论过，但我不记得当时我倾向于哪种语言了。<br><br><br><br>注意在固定指针（const pointer）中，&#8220;const&#8221;永远出现在&#8220;*&#8221;之后。例如：<br><br>
<div class=quote>
<div class=quote-title><u></u></div>
<div class=quote-content>int*const p1 = q; // 指向int变量的固定指针<br><br>intconst* p2 = q; //指向int常量的指针<br><br>constint* p3 = q; //指向int常量的指针<br><br></div>
</div>
使用宏有什么问题？<br><br><br><br>宏不遵循C++中关于范围和类型的规则。这经常导致一些微妙的或不那么微妙的问题。因此，C++提供更适合其他的C++（译注：原文为the rest of C++，当指C++除了兼容C以外的部分）的替代品，例如内联函数、模板与名字空间。<br><br><br><br>考虑一下：<br><br>
<div class=quote>
<div class=quote-title><u></u></div>
<div class=quote-content>#include"someheader.h"<br><br><br><br>structS {<br><br>intalpha;<br><br>intbeta;<br><br>};</div>
</div>
<br><br>如果某人（不明智地）地写了一个叫&#8220;alpha&#8221;或&#8220;beta&#8221;的宏，那么它将不会被编译，或者被错误地编译，产生不可预知的结果。例如，&#8220;someheader.h&#8221;可能包含：<br><br>
<div class=quote>
<div class=quote-title><u></u></div>
<div class=quote-content>#definealpha 'a'<br><br>#definebeta b[2]<br><br></div>
</div>
将宏（而且仅仅是宏）全部大写的习惯，会有所帮助，但是对于宏并没有语言层次上的保护机制。例如，虽然成员的名字包含在结构体的内部，但这无济于事：在编译器能够正确地辨别这一点之前，宏已经将程序作为一个字符流进行了处理。顺便说一句，这是C和C++程序开发环境和工具能够被简化的一个主要原因：人与编译器看到的是不同的东西。<br><br><br><br>不幸的是，你不能假设别的程序员总是能够避免这种你认为&#8220;相当白痴&#8221;的事情。例如，最近有人报告我，他们遇到了一个包含goto的宏。我也见过这种情况，而且听到过一些——在很脆弱的时候——看起来确实有理的意见。例如：
<div class=quote>
<div class=quote-title><u></u></div>
<div class=quote-content><br><br>#defineprefix get_ready(); int ret__<br><br>#defineReturn(i) ret__=i; do_something(); goto exit<br><br>#definesuffix exit: cleanup(); return ret__<br><br><br><br>voidf()<br><br>{<br><br>prefix;<br><br>//...<br><br>Return(10);<br><br>//...<br><br>Return(x++);<br><br>//...<br><br>suffix;<br><br>}<br><br></div>
</div>
作为一个维护的程序员，就会产生这种印象；将宏&#8220;隐藏&#8221;到一个头文件中——这并不罕见——使得这种&#8220;魔法&#8221;更难以被辨别。<br><br><br><br>一个常见的微妙问题是，一个函数风格的宏并不遵守函数参数传递的规则。例如：
<div class=quote>
<div class=quote-title><u></u></div>
<div class=quote-content><br><br>#definesquare(x) (x*x)<br><br><br><br>voidf(double d, int i)<br><br>{<br><br>square(d); //好<br><br>square(i++); //糟糕：这表示 (i++*i++)<br><br>square(d+1); //糟糕：这表示(d+1*d+1); 也就是 (d+d+1)<br><br>//...<br><br>}</div>
</div>
<br><br>&#8220;d+1&#8221;的问题，可以通过在&#8220;调用&#8221;时或宏定义时添加一对圆括号来解决：<br><br>
<div class=quote>
<div class=quote-title><u></u></div>
<div class=quote-content>#definesquare(x) ((x)*(x)) /*这样更好 */</div>
</div>
<br><br>但是， i++被执行了两次（可能并不是有意要这么做）的问题仍然存在。<br><br><br><br>是的，我确实知道有些特殊的宏并不会导致C/C++预处理宏这样的问题。但是，我无心去发展C++中的宏。作为替代，我推荐使用C++语言中合适的工具，例如内联函数，模板，构造函数（用来初始化），析构函数（用来清除），异常（用来退出上下文环境），等等。 
<img src ="http://www.cppblog.com/CrazyDev/aggbug/115698.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/CrazyDev/" target="_blank">CrazyDev</a> 2010-05-18 17:12 <a href="http://www.cppblog.com/CrazyDev/articles/115698.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>C++与Flash的交互</title><link>http://www.cppblog.com/CrazyDev/articles/115651.html</link><dc:creator>CrazyDev</dc:creator><author>CrazyDev</author><pubDate>Tue, 18 May 2010 02:36:00 GMT</pubDate><guid>http://www.cppblog.com/CrazyDev/articles/115651.html</guid><wfw:comment>http://www.cppblog.com/CrazyDev/comments/115651.html</wfw:comment><comments>http://www.cppblog.com/CrazyDev/articles/115651.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/CrazyDev/comments/commentRss/115651.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/CrazyDev/services/trackbacks/115651.html</trackback:ping><description><![CDATA[<p>研究Flash嵌入游戏中的可行性.......</p>
<p>渲染问题已解决 <br>事件响应已解决 <br>下面是C++与Flash AS的交互, 以MFC为例: <br>1. 新建一个MFC Dialog程序</p>
<p>2. 添加一个Flash控件</p>
<p><img alt="" src="http://p.blog.csdn.net/images/p_blog_csdn_net/xoyojank/EntryImages/20081022/CppFlash_AddActiveX.JPG"></p>
<p><img alt="" src="http://p.blog.csdn.net/images/p_blog_csdn_net/xoyojank/EntryImages/20081022/CppFlash_AddFlash.JPG"></p>
<p>3. 把Flash控件添加一个变量</p>
<p><img alt="" src="http://p.blog.csdn.net/images/p_blog_csdn_net/xoyojank/EntryImages/20081022/CppFlash_AddVariable.JPG"><br><br><img alt="" src="http://p.blog.csdn.net/images/p_blog_csdn_net/xoyojank/EntryImages/20081022/CppFlash_AddflashUI.JPG"></p>
<p>4. 在OnInitDialog()中添加载.swf文件</p>
<p>&nbsp;<!--startfragment --> <img src="file:///C:/DOCUME~1/Yuan/LOCALS~1/Temp/%25QT13(ZV[L4%25%7B%7D_U0%7BJ3~TV.jpg"> </p>
<p>5. 制作一个flash, 放一个Button上去, 导出一下就可以在MFC中看到了</p>
<p><img alt="" src="http://p.blog.csdn.net/images/p_blog_csdn_net/xoyojank/EntryImages/20081022/CppFlash_FlashButton.JPG"><br><br><img alt="" src="http://p.blog.csdn.net/images/p_blog_csdn_net/xoyojank/EntryImages/20081022/CppFlash_MFCDialog.JPG">&nbsp;</p>
<p>6. flash调用C++. </p>
<p>这个很简单, 在flash的那个Button组件的动作中添加脚本:</p>
<p><!--startfragment -->&nbsp;<img src="file:///C:/DOCUME~1/Yuan/LOCALS~1/Temp/E()UIB]N[%7DLL0A9$%25H205RW.jpg">&nbsp;&nbsp;</p>
<p>然后在MFC中添加事件响应:</p>
<p><img alt="" src="http://p.blog.csdn.net/images/p_blog_csdn_net/xoyojank/EntryImages/20081022/CppFlash_AddEvent.JPG" width=471 height=341><br><br><img alt="" src="http://p.blog.csdn.net/images/p_blog_csdn_net/xoyojank/EntryImages/20081022/CppFlash_AddEventMethod.JPG"></p>
<p><!--startfragment -->&nbsp;<img src="file:///C:/DOCUME~1/Yuan/LOCALS~1/Temp/~AXX%7D%25BR2AB8CH8W$BF149L.jpg">&nbsp;&nbsp;</p>
<p>7. C++调用Flash. </p>
<p>首先在Flash中注册回调函数:</p>
<p>&nbsp;<!--startfragment --> <img src="file:///C:/DOCUME~1/Yuan/LOCALS~1/Temp/PS~TFW1IBUFL7T5G_8AMC1N.jpg">&nbsp;&nbsp;</p>
<p>然后在C++中添加调用:</p>
<p><!--startfragment -->&nbsp;<img src="file:///C:/DOCUME~1/Yuan/LOCALS~1/Temp/(J2OJZNHWKEMJ_CRS]%60SI@4.jpg"> <br><br>另外, 还可以通过GetVariable()和SetVariable()来设置flash中定义的变量</p>
<p>8. 导出一下flash, 编译一下C++, 就可以看到效果了:</p>
<p><img alt="" src="http://p.blog.csdn.net/images/p_blog_csdn_net/xoyojank/EntryImages/20081022/CppFlash_CallCpp.JPG"><br><br><img alt="" src="http://p.blog.csdn.net/images/p_blog_csdn_net/xoyojank/EntryImages/20081022/CppFlash_CallFlash.JPG"></p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>本文来自CSDN博客，转自：<a href="http://blog.csdn.net/xoyojank/archive/2008/10/22/3122679.aspx">http://blog.csdn.net/xoyojank/archive/2008/10/22/3122679.aspx</a></p>
<img src ="http://www.cppblog.com/CrazyDev/aggbug/115651.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/CrazyDev/" target="_blank">CrazyDev</a> 2010-05-18 10:36 <a href="http://www.cppblog.com/CrazyDev/articles/115651.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>调试游戏程序的学问（转）</title><link>http://www.cppblog.com/CrazyDev/articles/113737.html</link><dc:creator>CrazyDev</dc:creator><author>CrazyDev</author><pubDate>Tue, 27 Apr 2010 12:48:00 GMT</pubDate><guid>http://www.cppblog.com/CrazyDev/articles/113737.html</guid><wfw:comment>http://www.cppblog.com/CrazyDev/comments/113737.html</wfw:comment><comments>http://www.cppblog.com/CrazyDev/articles/113737.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/CrazyDev/comments/commentRss/113737.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/CrazyDev/services/trackbacks/113737.html</trackback:ping><description><![CDATA[链接：<a id=ctl00_MainHeader_ArticleAuthor href="http://blog.csdn.net/fannyfish/"><strong>http://blog.csdn.net/fannyfish/</strong></a><br>
<div id=ctl00_MainContent_Contents>
<h2>版权声明</h2>
<ul>
    <li>作者：Steve Rabin, Nintendo of America Inc.
    <li>邮箱：steve@aiwisdom.com
    <li>译者：沙鹰
    <li>校对：万太平 </li>
</ul>
<h2>概述</h2>
<p>调试游戏程序，和调试任何其它软件的代码一样，都可能是一项艰巨的任务。一般说来，有经验的程序员能迅速地识别并纠正哪怕是最难的bug，但是对于新手而言，改bug可能更像是一件难以处理的，并且容易使人灰心丧气的任务。更糟的是，当你初步着手开始寻找bug的根源时，永远也不会知道究竟要花费多长时间才能找到。此时不必慌张，要像个训练有素的程序员，集中精力寻找bug。一旦你消化了本文介绍的技巧和知识，你将能够击退最&#8220;凶猛&#8221;的bug，重获对游戏的控制。</p>
<p>运用本文描述的五步调试法，困难的调试过程也可能变得简单一些。训练有素地运用该方法，将确保你花费最少的时间在寻找和定位每一个bug上。在你着手对付一些有难度的bug时，牢记一些专家技巧也很重要，因此本文也收集了一些有价值的、经过时间考验的技巧。然后本文还列出了一些有难度的调试情境，解释了当遇到一些特定的bug模式时应当做些什么。因为好的工具对于调试任何游戏都很重要，本文还将讨论一些特定的工具，你可将这些工具嵌入你的游戏中，从而帮助调试一些游戏编程所独有的调试情形。最后让我们回顾一些在前期预防bug的简单技术。</p>
<h2>五步调试法</h2>
<p>老练的程序员们具有一种超能力，能够迅速地、驾轻就熟地捕捉到即使是最不可思议的bug。他们总是神奇地、近乎直觉地知道错误源自何方，这一点实在令人敬畏。他们之所以显得天才，除了因为拥有丰富的经验外，还因为他们对于勘探和减少需排查的可能的原因的方法训练有素、融会贯通。下面给出的五步调试法旨在重现他们所熟练掌握的技能，助你在跟踪bug的问题上形成一种有系统的、且注意力集中的风格。</p>
<h3>第一步：始终如一地重现问题</h3>
<p>不论是什么bug，重要的是，你应当了解如何能够始终如一地重现它。</p>
<p>试图纠正一个随机出现的bug常会使人感到挫败，而且通常不过是浪费时间。事实是，几乎所有的bug都会在特定的情境下可靠地重现，因此发现这个情景和规律就成为你的、或贵公司测试部同仁的工作。</p>
<p>让我们举一个假想的游戏bug为例，在测试员报告里写道：&#8220;有时候，游戏会在玩家杀死敌人时死机（Crash）。&#8221;不幸地，像这样的bug报告太过于含糊，而且由于这个问题看上去不是百分之百会出现的，多数时候玩家仍可以正常地摧毁敌人。因此当游戏crash时，必然还有一些其它相关因素。</p>
<p>对于不容易重现的bug，理想情形是创建一系列&#8220;重现步骤（Repro Steps）&#8221;，说明每次应怎样才能重现bug。例如，下面的步骤极大地改善了之前的bug报告。</p>
<p>重现步骤：</p>
<ol>
    <li>开始单人游戏。
    <li>选择在第44号地图上进行Skirmish也就是多人练习模式的游戏。
    <li>找到敌人营地。
    <li>在一定的距离开外，使用投射类武器（Projectile Weapon）攻击在营地里的敌人。
    <li>结果：90％的时候游戏死机。 </li>
</ol>
<p>显然，重现步骤是一种很好的方法，测试人员藉此帮助其他人重现bug。不过，精简可能导致bug发生的事件链（Chain of Events）的过程也是至关重要的，其原因有三。第一：对当时bug为何发生提供了有价值的线索。第二：提供了一种比较系统地测试bug是否已被彻底改正的方法。第三：可用于回归测试，确保bug不再卷土重来。</p>
<p>尽管这里的信息没有告诉我们bug的直接诱因，它使我们能够始终重现bug。一旦你确定了bug发生的环境，你就可以进行下一步骤，开始搜集有用的线索。</p>
<h3>第二步：搜集线索</h3>
<p>现在你能够可靠地使bug重现，下一步请你戴上侦探的鸭舌帽并搜集线索。每条蛛丝马迹都是排除一个可能的原因并缩短疑点列表的机会。有了足够的线索，bug的发源地会变得明显。因此为了明了每条线索并理解其潜台词，付出的努力是值得的。</p>
<p>不过有一点要注意，你应当总是在心里质疑每一条已发现的线索，是不是误导的，或不正确的。举例来说，我们被告知某个bug总发生在爆炸之后。尽管这可能是一条非常重要的线索，但它仍然可能是一个虚假的误导。时刻准备着放弃那些与收集来的信息冲突的线索。</p>
<p>还是以上面的bug报告为例，我们了解到游戏的crash发生在玩家使用投射类武器攻击某个特定的敌人营地的时候。究竟关于投射类武器和从远处攻击这两者，有什么特别之处？这是需要深思的重点，但也不要耗费太多时间思考。亲临其境，观察错误究竟是如何发生的，因为我们需要获取更多的确凿的证据，而留连于表面的线索是获得实际证据最不有效的方式。</p>
<p>在本例中，当我们进入游戏，并实际观察错误的发生时，我们会发现游戏死机发生在一个&#8220;箭&#8221;对象里，错误的症状是一个无效指针。进一步的检查显示，该指针本来是应当指向那个发射此箭的角色的。在此情况下，这支箭原本要向其发射者报告它击中了某个敌人，使发射者为该次成功的攻击获得一定的经验值。但尽管看上去找到了原因所在，我们对真实的潜原因仍然一无所知。我们必须首先找出是什么扰乱了这个指针。</p>
<h3>第三步：查明错误的源头</h3>
<p>当你认为收集到的线索已经够多时，就到了专注于搜索和查明错误的源头的时候了。有两个主要方法，第一个方法是先提出关于bug发生原因的假设，接着对该假设进行验证（或证明它不正确）；第二个方法是较为系统的分而治之的方法。</p>
<p><strong>方法1：假设法</strong></p>
<p>搜集了足够的线索，你会开始怀疑有些什么事情导致了bug发生。这就是你的假设（Hypothesis）。当你能够在心里清楚地陈述这假设，你就可以开始设计一些能验证该假设，或反证证明该假设不正确的测试用例。</p>
<p>在我们的例子里，通过测试得出了以下线索和关于游戏设计的信息：</p>
<ul>
    <li>当一支箭射出的时候，该箭被赋予一个指向射箭人的指针。
    <li>当一支箭射中某个敌人的时候，将奖励送给射箭人。
    <li>游戏死机发生在一支箭试图通过一个无效指针向射箭人传回奖励。 </li>
</ul>
<p>我们的第一个假设可能是这样，指针的值在箭的飞行途中被损坏。基于此种假设，我们开始设计测试，并搜集数据来支持或推翻此原因。例如我们可以让每一支箭都将射箭人的指针注册到同一个备份区域。当我们又捕捉到crash时，可以检查备份下来的数据，看无效指针的值是否与这支箭在被射出的时候所赋予的值相同。 &nbsp;</p>
<p>不幸的是在我们所举的例子里，最后发现这条假设是不正确的。备份的指针和导致游戏死机的指针具有相同的值。这样一来，我们就面临着一个抉择。是再提一个假设并进行验证，还是重头寻找更多的线索？现在让我们试着再提一条假设。</p>
<p>如果箭的发射人指针从没有被破坏（新线索），或许从箭射出到箭射中敌人的这段时间里，这个发射人被删除了。为了检查这点，让我们记录下敌人营地里死亡的每个角色的指针。当crash发生时，我们可以将出错指针和死亡并从内存中删除的敌人的列表进行比较。这样进行，很快就证实原因正是如此。射箭人死时，箭还在飞行途中。</p>
<p><strong>方法2：分治法</strong></p>
<p>两个假设使我们找出了bug，同时也表现了分而治之的概念。我们知道指针的值无效，但我们不知道它是因为值被修改过而损坏，或者这个指针在更早些的时候就已经无效。通过测试第一个假设，我们排除了两个可能性中的一个。像歇洛克&#183;福尔摩斯（Sherlock Holmes）曾说过的：&#8220;&#8230;&#8230;当你排除了不可能的情况后，其余的情况，尽管多么不可能，却必定是真实的。&#8221;[译注：绿玉皇冠案（柯南&#183;道尔）]</p>
<p>有人将分而治之的方法简单形容为确定故障发生的时刻，并从输入开始回溯而发现错误。比如有一个并不会造成死机的bug，在某个时刻发生的初始错误将影响层层传递，最终导致故障发生。确定初始错误通常通过在所有输入分支上设置（有条件或无条件的）断点（Breakpoint）来进行，直到找到那个不能正常输出——也就是导致bug的输入。</p>
<p>当从故障发生的时刻开始回溯，你在局部变量和栈里面的上级函数中寻找任何异常。对于死机bug来说，通常你会试图寻找一个空值（NULL）或极大的数字值。如果是关于浮点数的bug，在栈上寻找NAN或极大的数字。</p>
<p>无论是对问题进行有根据的推测，检验假设，还是有系统地搜捕肇事代码，最终你会找到问题所在。在这个过程中你要相信自己，并保持清醒。本文接下来的部分将详细讨论一些可用于在这步骤中的专门技术。</p>
<h3>第四步：纠正问题</h3>
<p>当我们发现bug的真正根源，接下来要做的便是提出和实现一个解决方案。无论如何，修改必须对项目所处的阶段是恰当的。例如，在开发的后期，通常不能只为了纠正一个bug，就修改底部的数据结构或程序体系结构。参照开发工作所处的阶段，主程序员或系统架构师将决定应当进行何种类型的修改。在关键的时刻，个别工程师（初级或中级）常常做出不好的决定，因为他们没有全盘考虑。</p>
<p>此外需要特别注意的是，理想情况下，代码的编写者应当负责修改自己代码里的bug。不过如果必须修改别人的代码，你至少应当在进行修改前和原作者进行讨论。讨论将使你了解一些方面，例如在以往对于类似的问题是怎么处理的，如果实施你的方案提议可能会造成什么影响等。总之，在未彻底理解由别人编写的代码的上下文前，急于进行修改是非常危险的。</p>
<p>继续讨论我们的例子，死机源于一个指向了一个不复存在的对象的无效指针。对此类问题模式的一个好的解决方案是使用一层间接引用，使crash不再发生。通常，正是因为这个理由，游戏使用对象的句柄而不是直接指针。这将是一个合理的修改。</p>
<p>但是，如果游戏项目因为某个里程碑、或一个重要的演示版交付日迫在眉睫，而需要快速完成修改，你可能会倾向于对现有的特殊情况实现一个较为直接的修改方案（例如让射箭者在自身被删除的时候使其射出的箭中关于自身的指针失效）。如果在程序里打上了这一类的快速补丁（Quick Hack），你要记得将有关的注释文档化，以使其在这截止期限后被重新评估。开发中这样的情况屡见不鲜：快速补丁被人们遗忘，而在几个月后才造成了难于发现和解决的麻烦。</p>
<p>虽然看上去我们发现了bug并且确定了一种修改（使用句柄而非指针），探索其他可能造成同样问题出现的途径是很关键的。这虽然需要额外的时间，但是为了确保bug从根本上被消灭，而非只是消除了bug的一种表现形式，这努力是值得的。在我们的例子中，可能其他类型的投射类武器同样会造成游戏死机，但其它非武器对象的关系、甚至角色之间的关系也会受到同一个设计缺陷的影响。应找出所有这些相关的场合，使你的修改方案针对的是问题的核心，而非仅仅是问题的某一种征兆。</p>
<h3>第五步：对所作的修改进行测试</h3>
<p>解决方案实施后，还必须进行测试以确认它的确修补了错误。第一步要确保先前有效的重现步骤不会导致bug重现。通常应当让bug修改者以外的其他人，例如测试员，独立地确认bug被修复与否。</p>
<p>第二步还要确保没有新的bug被引入游戏。你应当让游戏运行一段可观的时间，确保所作的修改没有影响其它部分。这是非常重要的，因为很多时候，尤其是在项目开发周期接近尾声的时候，为修改bug所作的改动，会导致其他系统出错。在项目的后期，你还应当让主程序员或其他开发者来检视每一个修改，这额外的可靠性检验要保证新的修改不会对版本有负面影响。</p>
<h2>高级调试技巧</h2>
<p>如果你遵循以上所述的基本调试步骤，你应能找到并修复大多数bug。不过在你尝试提出假设、验证/否决一个候选的原因、或者尝试找出出错位置的时候，或许你会愿意考虑下列的技巧。</p>
<h3>分析你的假设</h3>
<p>调试程序的时候要保持心胸开阔是很重要的，而且不要作太多假设。如果你假设某些貌似简单的东西总是正确的，你可能就过早地缩小了搜索范围，从而完全错过了找出真相的机会。举例来说，不要总是想当然地认为你正在使用最新的软件或程序库。检验你的假设是否正确常常是值得的。</p>
<h3>将交互和干扰最小化</h3>
<p>有时，多个系统之间会以某种方式交互，这会使调试复杂化。试试看关闭那些你认为和问题无关的子系统（例如，关闭声音子系统），从而将系统之间的交互降到最低限度。有时候这有助于识别问题，因为原因可能就在你关闭的系统中，这样你就知道接下来该看那里。</p>
<h3>将随机性最小化</h3>
<p>通常，bug之所以难于重现，要归咎于从帧速率和实际随机数等方面引入的可变性。如果你的游戏没有采取固定的帧速率，试试看将&#8220;在每帧内流逝的时间&#8221;锁定为常量。至于随机数，可以关闭随机数发生器，或给它固定的常数作为随机发生种子，这样每次运行都会得到同样的序列。不幸地是，玩家会给游戏带来无法控制的显著的随机性。如果连这玩家带来的随机性也必须得到控制，请考虑将玩家的输入记录下来，从而能以可预料的方式将输入记录直接送入游戏［Dawson01］。</p>
<h3>将复杂的计算拆分成几步进行</h3>
<p>若某行代码含有大量计算，或许将这行拆分为多个步骤会有助于识别问题。例如，可能其中的某小段计算产生了类型转换错误，或某个函数并未返回你期望它返回的值，或运算进行的顺序并不是你所想的那样。这也使你能够检查每一步中间过程的计算。</p>
<h3>检查边界条件</h3>
<p>几乎我们中的每一个人都曾被经典的&#8220;差一错误&#8221;（Off-by-one）问题折磨过。要检查算法的边界条件，特别是在循环结构中。</p>
<h3>分解并行计算</h3>
<p>如果你怀疑程序里的竞争条件（Race Condition，不同的执行顺序会产生不同的结果），试试看将代码改写为串行的，然后检查bug是否消失。在线程中，增加额外的延迟，观察是否问题也随之变化。问题范围能缩小——若你能够确定问题是竞争条件，并通过试验将问题孤立出来。</p>
<h3>充分利用调试器提供的工具</h3>
<p>明白和懂得如何使用条件断点、内存watch、寄存器watch、栈，以及汇编级/混合调试。工具能帮你寻找线索和确凿的证据，这是识别bug的关键。</p>
<h3>检查新近改动的代码</h3>
<p>调试也可以通过源代码版本控制来进行，这真是一个令人惊讶的方法。如果你清楚地记得在某个日期前程序还是工作的，但是从某天开始就失灵了，你就可以专注于期间改动过的代码，从而较快地找到引入缺陷的代码段。至少，也可以将搜索范围缩小至某个特定子系统，或某几个文件。</p>
<p>另一个利用版本控制的方法是生成游戏在bug出现之前的一个版本。当你看不清问题的时候这尤其有用。将新老版本分别在调试器中运行，将值互相比较，你就可能找出问题的关键所在。</p>
<h3>向其他人解释bug</h3>
<p>常常在你向他人解释bug的时候，你会追忆起一些步骤，并意识到一些遗漏或忘记检查的地方。与其他的程序员交流的益处还在于他们可能会精辟地提出别样的值得检验的假设。不要低估和他人交谈的作用，也永远不要羞于寻求他人的建议。你团队中的同事是你的伙伴，也是你与最有难度的bug战斗时最精良的武器之一。</p>
<h3>和同事一起调试</h3>
<p>这通常是很合算的，因为每个人在对付bug上都有自己的独门经验和策略。你也能学到新的技术，学会从从未尝试过的角度入手处理bug。让某人看着你进行调试，这可能是追捕bug最有效的方法之一。</p>
<h3>暂时放下问题</h3>
<p>有的时候，你已经如此接近问题，以至于无法再清楚全面地看待它。试试看改变一下环境，出门闲逛一下。当你放松，再回到问题上，你可能会有新的认识。有时候，当你决定让自己休息一下时，你的心里下意识地还在思考问题，过后答案就自然浮现了。</p>
<h3>寻求外部的帮助</h3>
<p>获得帮助有多种很好的途径。如果是在开发视频游戏，那么每家游戏机制造商都有一整班的人，他们将在你遇到麻烦的时候协助你。了解他们的联系方式。三大游戏机制造商现在都提供电话支持、电子邮件支持、和开发者互相帮助的新闻讨论组。</p>
<h2>困难的调试情景和模式</h2>
<p>消灭bug常有模式可循。在艰苦的调试情景中，模式是关键。在此经验起了很大作用。如果你曾经见过某个模式，你就可能迅速地找出bug所在。希望下列情景和模式能给你一些方向。</p>
<h3>Bug仅在发布版里出现，调试版则正常</h3>
<p>通常，Bug只出现于发布版（Release Build）中意味着这是数据未初始化，或与代码优化有关的bug。一般来说，即使你没有特地编写进行初始化的代码，调试版（Debug Build）也会自动将变量初始化为零。而这隐式初始化在发布版中是不存在的，因而出现了bug。</p>
<p>找出原因的另一个策略是：在调试版里，慢慢地逐一打开优化开关。对每一点优化都进行测试，你可以找到罪魁祸首。例如，在调试版里，函数一般都不是内联的。但在优化后有些函数自动进行了内联，有时某个bug就这样发作了。</p>
<p>还有一点值得注意的是，在发布版中也可以打开调试符号（Debug Symbol）。这使得在一定程度上（虽然一般并不）对优化过的代码进行调试成为可能，你甚至可以让一部分调试系统保持开启。举例来说，你可以让你的异常处理函数在崩溃的现场执行一个全面的堆栈回溯（这需要符号）。这是非常有用的，因为当测试员必须运行优化过的游戏版本的时候，你还是可以回溯程序崩溃。</p>
<h3>在作了一些无害的改动后，bug不见了</h3>
<p>如果bug在一些完全无关的改动（例如添加了一行无害的代码）后不见了，那么这就像是一个时序问题，或内存覆盖问题。尽管表面上bug已经消失了，但是实际上可能只是转移到了代码的另一个部分。不要错过这个找出bug的机会。Bug就在那儿，将来迟早有一天它肯定会不知不觉地、狡猾地害你。</p>
<h3>确实具有间歇性的问题</h3>
<p>像前面提过的那样，许多问题会在合适的环境下稳定地重现。但如果你无法控制环境，那就必须要趁问题抬起它丑陋的小脑袋时抓住问题。这里的关键是在捕捉到问题的时候要记下尽可能多的信息，以便随后可在必要时检查。机会可不是很多的，因此要充分利用每一次出错的机会。还有一个有效的技巧就是将程序出错时收集得到的数据和程序正常时收集的数据进行比较，发现其中差异。</p>
<h3>无法解释的行为</h3>
<p>有时当你在单步执行代码的时候，却发现变量自说自话地被修改了。这种真正怪异的现象通常表示系统或调试器失去了同步。解决方案是试试看&#8220;加快清除缓存的频率&#8221;，使系统重获同步。</p>
<p>感谢Scott Bilas为清除缓存归纳出如下的&#8220;四重&#8221;方针。</p>
<ul>
    <li>重试（Retry）：清除游戏的当前状态再运行。
    <li>重建（Rebuild）：删除已编译过的中间对象，并进行彻底的版本重建。
    <li>重启（Reboot）：通过硬复位，将你机器里的内存擦除。
    <li>重装（Reinstall）：通过重装，恢复你的工具和操作系统中的文件和设置。 </li>
</ul>
<p>在这&#8220;四重&#8221;里，&#8220;重建&#8221;是最重要的。有时候，编译器不能正确地识别代码间的依赖关系，导致受牵连的代码不能通过编译。症状常常是不可思议的怪异。一次彻底的重建有时就能解决问题。</p>
<p>处理这些无法解释的行为的时候，一定要预先猜测调试器会给出何种结果。通过printf函数输出并检验变量的实际值，因为调试器有时候会被迷惑，而无法准确地反映真实的值。</p>
<h3>编译器内部错误</h3>
<p>偶尔你会碰到这种情况，编译器承认它无法理解你的代码，从而抛出一个编译器内部错误（Internal Compiler Error）。这些错误可能显示在代码中存在合法性问题，也可能根本是编译器软件自身的问题（例如，超出了内存上限，或无法处理你如同天书一般的模板代码）。遇到编译器内部错误的时候，建议执行如下步骤：</p>
<ol>
    <li>进行完整的版本重建。
    <li>重启电脑，再进行一次完整的版本重建。
    <li>检查是否正在使用最新版本的编译器。
    <li>检查任何正在使用的库是否是最新版本。
    <li>试验同样的代码是否能在其他电脑上通过编译。 </li>
</ol>
<p>如果这些步骤不能解决问题，试试确定究竟是那段代码引起了错误。如果可能的话，用分治法减少编译到的代码，直至编译器内部错误消失。当故障的位置已经确定后，检视这段代码并保证它看上去没错（最好能多请几个人读它）。如果代码看上去的确合理，下一步试着重新组织一下代码，希望编译器能报告出更有意义的错误信息。最后你还可以尝试用旧版本的编译器来编译。很可能在最新版的编译器里存在bug，而使用旧版本的编译器就能顺利完成编译。</p>
<p>如果这些办法都不奏效，试试看在网上搜索相似的问题。如果还是没有用，向编译器的制造商寻求额外的帮助。</p>
<h3>当你怀疑问题不是出在自己的代码里</h3>
<p>不象话，应该总是怀疑自己的代码！不过，如果你确信不是你们的代码的问题，最好的行动方针是到网站上寻找所使用的函数库或编译器的更新补丁。详细阅读其readme文件，或者在网上搜索关于此函数库或编译器的已知问题。很多时候，其他的人也碰到了相似的问题，解决办法或补丁也已经有了。</p>
<p>不过，你发现的bug来自他人提供的函数库，或来自有故障的硬件（碰巧你是第一个发现它的人）的几率不大。虽然不太可能，但有时还是会发生的。最快解决方法是编写一小段例程将问题隔离开来。然后你可以把这段程序email给函数库的作者，或硬件生产商，以便他们进一步就此问题进行调查。如果这真是其他人造成的bug，由于你的帮助，他人可以快速地识别和重现问题，从而bug以最快速度得到改正。</p>
<h3>理解底层系统</h3>
<p>有时为了找到一些难度很高的bug，你必须了解底层系统。仅仅通晓C或C++还远远不够。为了成为一个优秀的程序员，你必须懂得编译器是如何实现较高层次的概念，必须懂汇编语言，还必须了解硬件的细节（尤其是对游戏机游戏开发而言）。虽然认为高级语言掩盖了所有的复杂性并没有错，但是事实是当系统崩溃时你会感觉手足无措，除非你的理解深刻至抽象以下。若要进一步讨论高层抽象会如何造成隐患，请参见&#8220;The Law of Leaky Abstractions&#8221;［Spolsky02］。</p>
<p>那么，有哪些底层细节需要了解呢？就游戏而言，你应当了解如下事项：</p>
<ul>
    <li>了解编译器实现代码的原理。熟悉继承、虚函数调用、调用约定、异常是如何实现的。懂得编译器如何分配内存和处理内存对齐。
    <li>了解你所使用的硬件的细节。例如，懂得与某个特定硬件的高速缓存有关的问题（缓存中的数据何时会和主存储器中不同）、内存对齐的限制、字节顺序（Endianness，高位还是低位字节在前）、栈的大小、类型的大小（如整型int、长整型long、布尔型bool）。
    <li>了解汇编语言的工作原理，能够阅读汇编代码。这在调试器无法跟踪源代码时，例如在优化后的版本里查找问题时，很有帮助。 </li>
</ul>
<p>如不能牢牢掌握这些知识，在对付真正困难的bug的时候，你的致命弱点就会暴露出来。所以必须理解底层的系统，熟悉其规则。</p>
<h3>增加有助于调试的基础设施</h3>
<p>没有合适的工具的帮助，在真空中调试程序必定会很费劲。解决办法是走另一个极端，直接将好的调试工具整合到游戏里。下列工具能极大地帮助修理bug。</p>
<h3>允许在运行中修改游戏变量</h3>
<p>调试和重现bug时，在运行中修改游戏变量的值的功能是非常有用的。实现此功能的经典界面是通过游戏中的一个调试命令行接口（CLI，Command-Line Interface）用键盘修改变量。按下某个键后，调试信息覆盖显示在游戏屏幕上，提示你用键盘进行输入。例如，当你想把游戏里的天气改成狂风暴雨，你可以在提示下输入&#8220;weather stormy&#8221;。此类界面在调节和检查变量的值或特定游戏状态的时候也很好用。</p>
<h3>可视化的AI诊断</h3>
<p>在调试中，好的工具是无价之宝，而标准调试器在诊断AI问题的时候总是那么力不从心。各种调试器虽然在某个具体时刻能给出很好的深度，但在解答AI系统怎样随着游戏进行而变化这个问题上完全无用。解决办法是在游戏里直接构造能够监控任意角色的诊断数据的可视化版本。通过将文字和3D线条组合起来，一些重要的AI系统如寻路（Pathfinding）、警觉边界（Awareness Boundaries）、当前目标等，会较容易跟踪和查错［Tozour02］［Laming03］。</p>
<h3>日志的能力</h3>
<p>通常，我们在游戏里有成堆的角色彼此交互和通讯，以得到非常复杂的行为。当交互失败，bug出现之时，关键在于能够记录导致bug的每个角色的个别状态及事件。通过对每个角色创建单独的日志，将带有时戳的关键事件记录下来，我们就可能通过检查日志来发现错误。</p>
<h3>记录和回放的能力</h3>
<p>像前面提到的那样，找出bug的关键在于可重现性。极致的可重现性需要通过记录和回放玩家的输入来实现［Dawson01］。对于那些概率很小的死机bug，记录和回放是找出确切原因的关键工具。但是为了支持记录和回放，你必须让游戏的行为是可预料的，也就是说对于同样的初始状态，同样的玩家输入必定会得到同样的输出结果。这并不意味着你的游戏对玩家来说是可以预知的，只是意味着你应当小心处理随机数的产生［Lecky-Thompson00］［Freeman-Hargis03］、初始状态、输入等方面，并能在程序崩溃时将输入序列保存下来［Dawson99］。</p>
<h3>跟踪存储分配事件</h3>
<p>这样实现你的存储分配算子，使其对每次分配操作都进行全面的栈跟踪。通过不断地记录究竟是谁在申请内存，你将不再有内存泄漏问题需要解决。</p>
<h3>崩溃时打印出尽可能多的信息</h3>
<p>&#8220;事后调试（Post-mortem Debug）&#8221;是很重要的。程序崩溃时，理想的情况下，你会希望能够捕捉到调用堆栈、寄存器以及所有其它可能相关的状态信息。这些信息可以显示在屏幕上，写入某个文件，或自动发送至开发者的电子信箱。这一类的工具让你迅速找出崩溃的源头，只消几分钟而不是几个小时。尤其是当故障发生在美工或策划同仁的机器上，而他们并不记得是怎样触发这次崩溃的时候。</p>
<h3>对整个团队进行培训</h3>
<p>虽然这并非一个能够编程实现的结构，但是你应当确定团队正确使用你创建的工具。请他们不要忽视错误对话框，确信他们知道怎样搜集信息从而不会丢失已找到的bug等等。花时间来培训测试员、美工、策划是值得的。</p>
<h3>预防bug</h3>
<p>关于调试的讨论，若没有一段文字指导如何在第一时间避免bug，便不能算完整。遵照这些指导方针，你或可避免编写出有bug的代码，或可在偶然之间发现自己不知不觉写出来的bug。不论是什么结果，都会最后帮你排除bug。</p>
<p>将编译器的警告级别（Warning level）调到最高，并指示将警告当作错误处理（Enable warnings as errors）。首先尽可能多地排除警告，最后才用#pragma将剩下的警告关闭掉。有时，自动类型转换及其它一些警告级的问题会带来潜在的bug。</p>
<p>使你的游戏能在多个编译器上编译通过。如果你确保游戏用多个编译器、面向多个平台都能编译通过，不同的编译器之间在警告和错误方面的差异将保证你的代码总体上更可靠。例如，编写任天堂GameCube&#8482;游戏机上的程序的人也可以在Win32下生成一个功能稍弱的版本。这也使你能够判断某个bug是否是具体平台所特有的。</p>
<p>编写你自己的内存管理器。这对于游戏机游戏是至关重要的。你必须清楚地知道正在使用那几块内存，并对内存上溢进行保护。由于内存溢出会带来一些最难查处的bug，首先确保不发生溢出是很重要的。在调试版本中使用预留的上溢和下溢保护内存块能使bug更早地暴露身份。对PC开发者来说，编写自己的内存管理器不是必须的，因为VC++里的内存系统功能已经很强了，而且还有像SmartHeap之类的好工具可以用来确定内存错误。</p>
<p>用assert来检验假设。在函数的开头加上assert来检验关于参数的假设（例如指针非空或范围检查）。另外，如果switch语句的default情况不应该被执行到，在其中加上assert。还有，标准assert可以被扩展以得到更好的调试性能［Rabin00b］。例如，让assert将调用堆栈打印出来是很有用的。</p>
<p>总是在声明变量的时候初始化它们。如果你无法在声明某个变量时赋予它一个有意义的值，那么就给它赋一个将来一眼就能认出它有没有被初始化过的容易辨认的值。有时候我们会用0xDEADBEEF、0xCDCDCDCD，或直接使用零。</p>
<p>总是将循环体和if语句体用花括号（{}）括起来。也就是将你所想的代码老老实实地包起来，使代码所实现的功能更直观。</p>
<p>变量起名要容易区分彼此。例如，m_objectITime和m_objectJTime看上去几乎一模一样。此类问题的典型例子是把&#8220;i&#8221;和&#8220;j&#8221;用作循环计数变量。&#8220;i&#8221;和&#8220;j&#8221;看上去很相似，你很容易把其中一个误认为另一个。可供选择的方法是，你可以用&#8220;i&#8221;和&#8220;k&#8221;，或者干脆使用更能描述其意义的名字。更多有关变量命名的认知差异的信息可以在［McConnell93］中找到。</p>
<p>避免在多处重复同样的代码。一模一样的代码同时出现在几个不同的地方，这是不利的。如果对其中一处代码作了改动，其余几个地方不一定也会被改动。如果看上去重复代码是必要的，重新考虑一下其核心功能，尽量将大多数的代码集中到一处。</p>
<p>避免使用那些中固定写死的&#8220;神奇数&#8221;（Magic numbers）。当单独一个数字出现在代码中，其意义可能是完全不为人知的。如果没有写注释，就无法让人理解之所以选择这个数字的理由，及这个数字代表什么。如果必须使用神奇数，将它们声明为有字面意义的常量或define。</p>
<p>测试的时候要注意代码覆盖率。在编写完一段代码之后，应验证它的每一个分支都能正确地执行。若其中一个分支从未被执行过，那么很可能其中正潜伏着bug。在测试不同分支的过程中你可能会发现这样一个bug，即其中某个分支是根本不可能被执行到的。这样的bug越早发现就越好。</p>
<h2>结论</h2>
<p>本文向你介绍了有效率地调试游戏所需的工具。调试有时候被形容为一门艺术，但那只是由于人们越有经验就做得越好。当你把五步调试法融会贯通，又学会了识别bug模式，并将自己的调试工具集成到游戏中，再形成自己在调试上的个人风格和绝招，很快地，你将熟练地有系统地追捕到并且消灭最困难的bug。最后再加上一点预防，我想你的游戏开发会一帆风顺，一个bug都没有也说不定。</p>
<h2>致谢</h2>
<p>感谢Scott Bilas和Jack Matthews，他们提了极好的建议，并为本文贡献了一些个人经验和智慧。人们看待调试有各自的角度，因此他们的意见在推敲本文建议的时候起了非常大的作用。</p>
<h2>参考文献</h2>
<ul>
    <li>[Dawson99] Dawson, Bruce, &#8220;Structured Exception Handling,&#8221; Game Developer Magazine (Jan 1999), pp. 52&#8211;54.
    <li>[Dawson01] Dawson, Bruce, &#8220;Game Input Recording and Playback,&#8221; Game Programming Gems 2, Charles River Media, 2001.
    <li>[Freeman-Hargis03] Freeman-Hargis, James, &#8220;The Statistics of Random Numbers,&#8221; AI Game Programming Wisdom 2, Charles River Media, 2003.
    <li>[Laming03] Laming, Brett, &#8220;The Art of Surviving a Simulation Title,&#8221; AI Game Programming Wisdom 2, Charles River Media, 2003.
    <li>[Lecky-Thompson00] Lecky-Thompson, Guy, &#8220;Predictable Random Numbers,&#8221; Game Programming Gems, Charles River Media, 2000.
    <li>[McConnell93] McConnell, Steve, Code Complete: A Practical Handbook of Software Construction, Microsoft Press, 1993.
    <li>[Rabin00a] Rabin, Steve, &#8220;Designing a General Robust AI Engine,&#8221; Game Programming Gems, Charles River Media, 2000.
    <li>[Rabin00b] Rabin, Steve, &#8220;Squeezing More Out of Assert,&#8221; Game Programming Gems, Charles River Media, 2000.
    <li>[Rabin02] Rabin, Steve, &#8220;Implementing a State Machine Language,&#8221; AI Game Programming Wisdom, Charles River Media, 2000.
    <li>[Spolsky02] Spolsky, Joel, &#8220;The Law of Leaky Abstractions,&#8221; Joel on Software, 2002, available online at www.joelonsoftware.com/articles/LeakyAbstractions.html.
    <li>[Tozour02] Tozour, Paul, &#8220;Building an AI Diagnostic Toolset,&#8221; AI Game Programming Wisdom, Charles River Media, 2002. </li>
</ul>
</div>
<img src ="http://www.cppblog.com/CrazyDev/aggbug/113737.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/CrazyDev/" target="_blank">CrazyDev</a> 2010-04-27 20:48 <a href="http://www.cppblog.com/CrazyDev/articles/113737.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item></channel></rss>