﻿<?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++博客-TheAnswer的C++博客-随笔分类-C++</title><link>http://www.cppblog.com/theanswerzju/category/5865.html</link><description>空有无可奈何落去花 却无似曾相识归来燕</description><language>zh-cn</language><lastBuildDate>Thu, 22 May 2008 17:26:55 GMT</lastBuildDate><pubDate>Thu, 22 May 2008 17:26:55 GMT</pubDate><ttl>60</ttl><item><title>C++ in Details -- Item 7: 静态绑定与动态绑定</title><link>http://www.cppblog.com/theanswerzju/archive/2008/01/12/41009.html</link><dc:creator>TheAnswer</dc:creator><author>TheAnswer</author><pubDate>Fri, 11 Jan 2008 22:53:00 GMT</pubDate><guid>http://www.cppblog.com/theanswerzju/archive/2008/01/12/41009.html</guid><wfw:comment>http://www.cppblog.com/theanswerzju/comments/41009.html</wfw:comment><comments>http://www.cppblog.com/theanswerzju/archive/2008/01/12/41009.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/theanswerzju/comments/commentRss/41009.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/theanswerzju/services/trackbacks/41009.html</trackback:ping><description><![CDATA[<p style="FONT-SIZE: 12pt; FONT-FAMILY: courier new">&nbsp;&nbsp;&nbsp;写这个Item挺偶然的，逛cppblog时看到这篇文章<a href="http://www.cppblog.com/sherrylso/archive/2007/11/11/36375.html">http://www.cppblog.com/sherrylso/archive/2007/11/11/36375.html</a>，评论里似乎讨论的很激烈，感觉挺有意思的，于是就笔痒，写下了这篇随笔。</p>
<p style="FONT-SIZE: 12pt; FONT-FAMILY: courier new">&nbsp;&nbsp;&nbsp;在讨论静态绑定与动态绑定前，需要先阐明两个概念：静态类型与动态类型。</p>
<p style="FONT-SIZE: 12pt; FONT-FAMILY: courier new">&nbsp;&nbsp;&nbsp;这两个概念只在变量是存在继承关系的类的对象的指针或者引用时才有意义，静态类型是你声明的类型，编译时即可知；而动态类型是指这个指针或者引用实际指向的对象的类型，运行时才可知。举几个例子：<br>假设D是从B公有继承的<br>&nbsp;&nbsp;&nbsp;B *bp1; // 静态类型 B* 动态类型无<br>&nbsp;&nbsp;&nbsp;B *bp2 = new B; // 静态类型 B* 动态类型 B*<br>&nbsp;&nbsp;&nbsp;B *bp3 = new D; // 静态类型 B* 动态类型 D*</p>
<p style="FONT-SIZE: 12pt; FONT-FAMILY: courier new">&nbsp;&nbsp;&nbsp;静态绑定的含义为函数的行为取决于对象的静态类型，而动态绑定的含义为函数的行为取决于对象的动态类型，有以下原则：<br>&nbsp;&nbsp;&nbsp;1. 非虚函数静态绑定。<br>&nbsp;&nbsp;&nbsp;2. 虚函数动态绑定。<br>&nbsp;&nbsp;&nbsp;3. 函数的缺省参数静态绑定。<br>&nbsp;&nbsp;&nbsp;4. 子类中仅需函数名即可覆盖父类的多个重载函数。</p>
<p style="FONT-SIZE: 12pt; FONT-FAMILY: courier new">&nbsp;&nbsp;&nbsp;Scott Meyers在《Effctive C++》2nd Edition中Item 37~38即是阐述这个问题的，大家可以参考一下，下面通过一段小程序做个说明。<br><br></p>
<div style="FONT-SIZE: 12pt; FONT-FAMILY: courier new">#include&nbsp;&lt;iostream&gt;<br><br>using&nbsp;namespace&nbsp;std;<br><br>class&nbsp;B<br>{<br>public:<br>&nbsp;&nbsp;&nbsp;&nbsp;virtual&nbsp;void&nbsp;func()<br>&nbsp;&nbsp;&nbsp;&nbsp;{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;cout&nbsp;&lt;&lt;&nbsp;"B::func()\n";<br>&nbsp;&nbsp;&nbsp;&nbsp;}<br><br>&nbsp;&nbsp;&nbsp;&nbsp;void&nbsp;func(double&nbsp;d)<br>&nbsp;&nbsp;&nbsp;&nbsp;{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;cout&nbsp;&lt;&lt;&nbsp;"B::func("&nbsp;&lt;&lt;&nbsp;d&nbsp;&lt;&lt;&nbsp;")\n";<br>&nbsp;&nbsp;&nbsp;&nbsp;}<br>};<br><br>class&nbsp;D&nbsp;:&nbsp;public&nbsp;B<br>{<br>public:<br>&nbsp;&nbsp;&nbsp;&nbsp;void&nbsp;func()<br>&nbsp;&nbsp;&nbsp;&nbsp;{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;cout&nbsp;&lt;&lt;&nbsp;"D::func()\n";<br>&nbsp;&nbsp;&nbsp;&nbsp;}<br><br>};<br><br>int&nbsp;main()<br>{<br>&nbsp;&nbsp;&nbsp;&nbsp;B&nbsp;b;<br>&nbsp;&nbsp;&nbsp;&nbsp;D&nbsp;d;<br>&nbsp;&nbsp;&nbsp;&nbsp;B&amp;&nbsp;rb&nbsp;=&nbsp;d;<br><br>&nbsp;&nbsp;&nbsp;&nbsp;d.func();<br>&nbsp;&nbsp;&nbsp;&nbsp;//d.func(2.3);<br><br>&nbsp;&nbsp;&nbsp;&nbsp;rb.func();<br>&nbsp;&nbsp;&nbsp;&nbsp;rb.func(2.3);<br><br>&nbsp;&nbsp;&nbsp;&nbsp;return&nbsp;0;<br>}<br></div>
<p style="FONT-SIZE: 12pt; FONT-FAMILY: courier new"><br>&nbsp;&nbsp;&nbsp;在VC9下的运行结果为<br>&nbsp;&nbsp;&nbsp;D::func()<br>&nbsp;&nbsp;&nbsp;D::func()<br>&nbsp;&nbsp;&nbsp;B::func(2.3)<br>&nbsp;&nbsp;&nbsp;如果去掉d.func(2.3);的注释，则编译即出错。</p>
<p style="FONT-SIZE: 12pt; FONT-FAMILY: courier new">&nbsp;&nbsp;&nbsp;为什么会有这样的结果呢？结合上面的4条原则来看，B中有两个版本的重载func函数，一个为虚函数，一个为非虚函数，D中重定义了继承而来的func()，但是根据原则4，原本也可以继承而来的func(double)也被D自己定义的func()给覆盖了，换言之，现在D中只有一个func()函数。由于d的静态和动态类型均为D，运行d.func(2.3)的时候发现根本找不到定义，只能编译报错。现在来看rb，由前所述，rb的静态类型为B，动态类型为D，于是rb.func()根据动态绑定表现为D::func()的行为，rb.func(2.3)根据静态绑定表现为B::func(double)的行为。</p>
<p style="FONT-SIZE: 12pt; FONT-FAMILY: courier new">&nbsp;&nbsp;&nbsp;我们可以来猜想以下为什么会有这样的代码，设计者原意可能是想仅重定义func()而继承func(double)的默认行为，可惜由于C++中覆盖规则，事与愿违。所以当混用虚函数与重载这两种不同时期决定的多态时，一定要小心。从OO设计的角度来说，虚函数几乎肯定是要被继承类重写的，而非虚函数是绝不应该被重定义的，所以当你的设计中出现重载函数不是同为虚函数或者非虚函数时，就要小心覆盖问题了。</p>
<p style="FONT-SIZE: 12pt; FONT-FAMILY: courier new">&nbsp;&nbsp;&nbsp;关于函数缺省参数问题，《Effective C++》2nd Editon Item 38讲得很清楚，这里就不赘述了。<img src="http://www.cppblog.com/CuteSoft_Client/CuteEditor/images/emsmile.gif" align=absMiddle border=0><br></p>
<p style="FONT-SIZE: 12pt; FONT-FAMILY: courier new">&nbsp;</p>
<img src ="http://www.cppblog.com/theanswerzju/aggbug/41009.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/theanswerzju/" target="_blank">TheAnswer</a> 2008-01-12 06:53 <a href="http://www.cppblog.com/theanswerzju/archive/2008/01/12/41009.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>C++ in Details -- Item 3 : 引用与函数</title><link>http://www.cppblog.com/theanswerzju/archive/2007/12/24/39509.html</link><dc:creator>TheAnswer</dc:creator><author>TheAnswer</author><pubDate>Mon, 24 Dec 2007 07:34:00 GMT</pubDate><guid>http://www.cppblog.com/theanswerzju/archive/2007/12/24/39509.html</guid><wfw:comment>http://www.cppblog.com/theanswerzju/comments/39509.html</wfw:comment><comments>http://www.cppblog.com/theanswerzju/archive/2007/12/24/39509.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/theanswerzju/comments/commentRss/39509.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/theanswerzju/services/trackbacks/39509.html</trackback:ping><description><![CDATA[<p style="FONT-SIZE: 12pt; FONT-FAMILY: courier new">&nbsp;&nbsp;&nbsp; 即使仅仅把C++看作"A Better C"，引用也是一个极其有价值的特性，在函数的参数表和返回值这两个领域，引用给了我们一个比传值更高效，比传指针更直观的新的选择。</p>
<p style="FONT-SIZE: 12pt; FONT-FAMILY: courier new">&nbsp;&nbsp;&nbsp; 引用是什么？这个问题其实并不容易回答，很多人会说引用是一个别名，这个理解似乎太过模糊。用Eckel的说法，引用就像是能自动被编译器间接引用的常量型的指针。你无需考虑它是否被初始化，也不用考虑怎样对其解引用，因为编译器会监督你做好前者，又会自动的帮你完成后者。一个引用总是和一块合法的存储单元相联系，这就是你唯一需要时刻牢记的。</p>
<p style="FONT-SIZE: 12pt; FONT-FAMILY: courier new">&nbsp;&nbsp;&nbsp; 先说函数参数吧。当你仅仅需要参数的值来做函数中的运算时，传递一个常量引用一般来说会好于值传递。首先是效率，值传递就意味着拷贝构造函数的调用，自然也有析构函数的开销，而这些都可以在传递引用的时候得以避免的；从函数调用者来看，使用传递引用的函数和传值传递的函数的调用形式并没有区别；对于继承体系而言，使用引用传递还可以避免切割或者分片问题，因为引用可以很好的实现向上类型转换。当然，如果只是需要使用值，你应该将引用声明为const。当需要改变外部对象的时候，引用的对手就变成了指针，两者在效率上可以视为同等，引用在函数的编写者的角度来看更自然，却容易让函数的调用者产生混淆，因为这种变换性被隐藏了。很难说谁好谁坏，就看具体情况的权衡吧。</p>
<p style="FONT-SIZE: 12pt; FONT-FAMILY: courier new">&nbsp;&nbsp;&nbsp; 从效率的角度来说，返回值当然也应该选择引用，不过当你返回局部对象的引用，或者函数内部用new初始化的指针的引用时，情况就变得很糟糕了。前者在函数调用者使用它的时候已经被销毁了，也就是你违背了引用总是和一块合法的存储单元相联系的准则，后者似乎解决了这个引用有效性的问题，不过很遗憾，引发了一个更大的问题：内存泄露，因为没有一个合适的机制去保证在正确的时间delete这个指针，每一次的函数调用都导致一点内存泄露，这是非常可怕的。其实原则也很简单，当你确实需要一个新的对象的时候，你就应该为其付出构造和析构的开销，最典型就是operator +, -, *, /函数。当然好心的编译器还是有可能帮你做返回值优化的，把函数返回的对象直接构造在你赋值的对象的内存上，一切又变得美好了。<img src="http://www.cppblog.com/CuteSoft_Client/CuteEditor/images/emsmile.gif" align=absMiddle border=0></p>
<br>
<img src ="http://www.cppblog.com/theanswerzju/aggbug/39509.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/theanswerzju/" target="_blank">TheAnswer</a> 2007-12-24 15:34 <a href="http://www.cppblog.com/theanswerzju/archive/2007/12/24/39509.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>C++ in Details -- Item 6 : 类设计者的核查表</title><link>http://www.cppblog.com/theanswerzju/archive/2007/12/24/39462.html</link><dc:creator>TheAnswer</dc:creator><author>TheAnswer</author><pubDate>Sun, 23 Dec 2007 18:54:00 GMT</pubDate><guid>http://www.cppblog.com/theanswerzju/archive/2007/12/24/39462.html</guid><wfw:comment>http://www.cppblog.com/theanswerzju/comments/39462.html</wfw:comment><comments>http://www.cppblog.com/theanswerzju/archive/2007/12/24/39462.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/theanswerzju/comments/commentRss/39462.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/theanswerzju/services/trackbacks/39462.html</trackback:ping><description><![CDATA[<span style="FONT-SIZE: 12pt; FONT-FAMILY: courier new">&nbsp;&nbsp;&nbsp; ym一把Adrew Koenig，《C++沉思录》绝对可以算C++领域经典著作。以下条款摘自其中第4章，下面对Meyers的引用是我自己加上的，那边讲得更为详细，Meyers总是会不厌其烦的解释他的每一个条款。<br>&nbsp;<br>&nbsp;&nbsp;&nbsp; 以下问题都没有确切的答案，关键是要提醒你思考牠们，并确认所做的事情是出于有意识的决定，而不是偶然事件。<br>&nbsp;<br>&nbsp;&nbsp;&nbsp; 1.你的类需要一个构造函数吗？<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 复杂的类需要构造函数来隐藏牠们内部的工作方式。<br>&nbsp;<br>&nbsp;&nbsp;&nbsp; 2.你的数据成员是私有的吗？<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Meyers98 Item20<br>&nbsp;<br>&nbsp;&nbsp;&nbsp; 3.你的类需要一个无参的构造函数吗？<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Meyers96 Item4<br>&nbsp;<br>&nbsp;&nbsp;&nbsp; 4.是不是每个构造函数初始化所有的数据成员。<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Meyers98 Item12<br>&nbsp;<br>&nbsp;&nbsp;&nbsp; 5.类需要析构函数吗？<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 该类是否分配了资源，而这些资源又不会由成员函数自动释放。<br>&nbsp;<br>&nbsp;&nbsp;&nbsp; 6.类需要一个虚析构函数吗？<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 是否会通过基类指针去删除派生类对象。<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Meyers98 Item14<br>&nbsp;<br>&nbsp;&nbsp;&nbsp; 7.你的类需要复制构造函数吗？<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 复制该类的对象是否就相当于复制其数据成员和基类对象。<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Meyers98 Item11<br>&nbsp;<br>&nbsp;&nbsp;&nbsp; 8.你的类需要一个赋值操作符吗？<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 如果需要复制构造函数，多半也会需要一个赋值操作符。<br>&nbsp;<br>&nbsp;&nbsp;&nbsp; 9.你的赋值操作符能正确的将对象赋给对象本身吗？<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Meyers98 Item17<br>&nbsp;<br>&nbsp;&nbsp;&nbsp; 10.你的类需要定义关系操作符吗？<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 逻辑上是否支持相等操作，类的值是否存在某种排序关系。<br>&nbsp;<br>&nbsp;&nbsp;&nbsp; 11.删除数组时你记住了用delete[]了吗？<br>&nbsp;<br>&nbsp;&nbsp;&nbsp; 12.记得在复制构造函数和赋值操作符的参数类型中加上const了吗？<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 复制对象不会改变原对象。<br>&nbsp;<br>&nbsp;&nbsp;&nbsp; 13.如果函数有引用参数，牠们应该是const引用吗？<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 只有当函数想改变参数时，才应该有不用const声明的引用参数。<br>&nbsp;<br>&nbsp;&nbsp;&nbsp; 14.记得适当地声明成员函数为const的了吗？<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 如果确信一个成员函数不用修改牠的对象，就可以声明牠为const，这样就可以把牠用于const对象了。<br>&nbsp;<br>&nbsp;&nbsp;&nbsp; 注1 Meyers96: Scott Meyers More Effective C++.&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp; 注2 Meyers98: Scott Meyers Effective C++ 2nd edition.<img src="http://www.cppblog.com/CuteSoft_Client/CuteEditor/images/emsmile.gif" align=absMiddle border=0><br><br></span>
<img src ="http://www.cppblog.com/theanswerzju/aggbug/39462.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/theanswerzju/" target="_blank">TheAnswer</a> 2007-12-24 02:54 <a href="http://www.cppblog.com/theanswerzju/archive/2007/12/24/39462.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>C++ in Details -- Item 5 : 内联真的那么美好吗</title><link>http://www.cppblog.com/theanswerzju/archive/2007/12/24/39461.html</link><dc:creator>TheAnswer</dc:creator><author>TheAnswer</author><pubDate>Sun, 23 Dec 2007 18:52:00 GMT</pubDate><guid>http://www.cppblog.com/theanswerzju/archive/2007/12/24/39461.html</guid><wfw:comment>http://www.cppblog.com/theanswerzju/comments/39461.html</wfw:comment><comments>http://www.cppblog.com/theanswerzju/archive/2007/12/24/39461.html#Feedback</comments><slash:comments>1</slash:comments><wfw:commentRss>http://www.cppblog.com/theanswerzju/comments/commentRss/39461.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/theanswerzju/services/trackbacks/39461.html</trackback:ping><description><![CDATA[<span style="FONT-SIZE: 12pt; FONT-FAMILY: courier new">&nbsp;&nbsp;&nbsp; 几乎每个C/C++程序员，都会把效率视为程序的生命线，以至于有时甚至不愿意承担函数调用的开销。是的，简单的一句函数调用，却意味着参数压栈，生成汇编语言的CALL，返回参数，执行汇编语言的RETURN这么多的额外开销，这一切可能是仅仅为了使用某个只有一行代码的函数体，更何况call指令常常会导致指令缓冲区被清空，引起局部性的丧失。这对视效率为生命的程序员来说，该是多么可怕的事情啊。<br>&nbsp;<br>&nbsp;&nbsp;&nbsp; 于是一些C/C++程序员发现了预处理器这个好东西，用宏来模拟一个函数调用，消除了所有开销，又似乎得到了和函数调用同样的效果。可惜好景不长，由于仅仅是简单的文本替换，会出现表达式在宏内展开时优先级背离期望，于是谨慎的程序员们只能把参数都用括号括起来，可是即使是这样，当面对参数求值的副作用时，还是显的无能为力。对于钟情于OO的C++程序员，他们发现了一个更糟糕的问题：用宏模拟的函数无法访问类的私有成员，以致于无法使其成为成员函数。<br>&nbsp;<br>&nbsp;&nbsp;&nbsp; 还好C++还提供了另一个利器：内联。只要在函数定义前简单的加上inline关键字，就可以用普通函数实现宏的效率，外加可预计的行为和类型安全，而且编译器还可以对函数体执行特定环境下的优化。你可以把定义放在头文件中，用于每个使用这个函数的文件而不会产生多个定义的错误。至于成员函数，就更美妙了，只要你在类的内部定义，就自动成为内联函数，甚至不需要额外声明，这种用法被大量的用为类成员的访问函数。<br>&nbsp;<br>&nbsp;&nbsp;&nbsp; 可是。。。你也许会问，内联真的那么美好吗？那为什么C++不默认所有函数都是内联函数，这样不是更省事吗？<br>&nbsp;<br>&nbsp;&nbsp;&nbsp; 还是那句老话，天下没有免费的午餐，在你享受内联给你带来的好处的同时，你也在为此付出代价，有些很明显，有些你却未必意识到。<br>&nbsp;<br>&nbsp;&nbsp;&nbsp; 最显而易见的，由于用函数体代替函数调用，目标代码的体积几乎不可避免的增大了，这意味着更多的内存占用。虚拟内存？是的，虚拟内存可以容纳更大的程序，但是过于庞大的代码体积很有可能导致频繁的页面调度甚至系统颠簸，一个在I/O上频繁操作的程序，你能指望牠快到哪里去呢？<br>&nbsp;<br>&nbsp;&nbsp;&nbsp; 和static或者const不一样，inline仅仅意味对编译器一个建议或者邀请，例如面对递归函数的时候，绝大多数编译器会无视你愚蠢的建议。被编译器拒绝的时候，会发生什么呢？现行C++标准规定这个情况下编译器就该像对待非内联函数一样，当你把定义放在头文件而用于多重编译单元的时候，多定义错误便是不可避免的。所以，请不要低估编译器的智慧，愚蠢的内联建议最后买单的还是你自己。<br>&nbsp;<br>&nbsp;&nbsp;&nbsp; 那编译器到底用什么准则来决定是否搭理你的inline建议呢？标准的答案是没有答案，但是有一些基本的原则可以作为参考。首先，函数太复杂的时候，编译器一般会放弃内联，例如上面提到的递归调用，函数内有循环，再或者函数体由许多语句组成，总之一旦编译器觉得不划算的时候牠就很理智的选择放弃；当显式或者隐式地取函数地址并且使用地址的，必然不会发生内联，你总不能指望编译器为你生成一个并不存在的函数的地址吧；构造函数和析构函数一般也不适合内联，因为这两个家伙经常会比你看到的函数体更为复杂，尤其是涉及继承时的层次建立与销毁的时候。还有一点也许出乎你的意料，内联是和函数调用而非函数本身联系在一起的，也就是说编译器完全可能在某些调用点使用内联而在其他调用点不予内联，这完全根据编译器对上下文的判断而定。<br>&nbsp;<br>&nbsp;&nbsp;&nbsp; 没有人能保证程序没有bug，所以才需要调试。可是，当调试器遇到内联函数的时候，你能想到一个好办法让其不晕菜吗？怎么在一个不存在的函数里设置断点呢？怎么单步执行到这样一个函数呢？怎么俘获对它的调用呢？求求你了，同情一下可怜的调试器吧。<br>&nbsp;<br>&nbsp;&nbsp;&nbsp; 其实只要认识到一点，内联的本质是一种优化，事情就变得简单了。优化是建立在程序本身的正确性的基础上的，而且一般而言，有那些通晓生成代码和执行环境的工具来执行往往比程序员效果更好。<img src="http://www.cppblog.com/CuteSoft_Client/CuteEditor/images/emsmile.gif" align=absMiddle border=0><br><br></span>
<img src ="http://www.cppblog.com/theanswerzju/aggbug/39461.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/theanswerzju/" target="_blank">TheAnswer</a> 2007-12-24 02:52 <a href="http://www.cppblog.com/theanswerzju/archive/2007/12/24/39461.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>C++ in Details -- Item 4 : 编译器的幕后行为</title><link>http://www.cppblog.com/theanswerzju/archive/2007/12/24/39460.html</link><dc:creator>TheAnswer</dc:creator><author>TheAnswer</author><pubDate>Sun, 23 Dec 2007 18:51:00 GMT</pubDate><guid>http://www.cppblog.com/theanswerzju/archive/2007/12/24/39460.html</guid><wfw:comment>http://www.cppblog.com/theanswerzju/comments/39460.html</wfw:comment><comments>http://www.cppblog.com/theanswerzju/archive/2007/12/24/39460.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/theanswerzju/comments/commentRss/39460.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/theanswerzju/services/trackbacks/39460.html</trackback:ping><description><![CDATA[<span style="FONT-SIZE: 12pt; FONT-FAMILY: courier new">&nbsp;&nbsp;&nbsp; 和C语言相比，C++的编译器除了做更严格的类型检查的工作外，似乎也变得更为勤快了，会在幕后默默帮你补全你看起来漏掉的函数。一个不了解编译器幕后行为的C++程序员简直和一个不知道自己汽车特性的司机一样可怕。本文就是为了消除这种可怕而写。<br>&nbsp;<br>&nbsp;&nbsp;&nbsp; 假设你定义了一个空类<br>&nbsp;&nbsp;&nbsp; class A {};<br>&nbsp;&nbsp;&nbsp; 在C++编译器通过它的时候，这个简单的空类也变得丰富多彩了，事实上你多了一个缺省构造函数，一个拷贝构造函数，一个赋值运算符，一个析构函数以及一对取地址运算符，怎么样，默默为你声明了这么多函数，C++编译器够勤快的了吧。不过天下没有免费的午餐，任劳任怨的编译器可能在不经意间已经给你设下了陷进。<br>&nbsp;<br>&nbsp;&nbsp;&nbsp; 1.缺省构造函数。实际上，它什么都不做，很安全，而且当你声明了至少一个构造函数的时候，编译器就不再生成缺省构造函数。<br>&nbsp;<br>&nbsp;&nbsp;&nbsp; 2.拷贝构造函数（赋值运算符）。缺省拷贝构造函数（赋值运算符）对类的非静态数据成员进行 "以成员为单位的" 逐一拷贝构造（赋值）。也就是说会对类中的非静态数据成员进行递归的拷贝构造函数（赋值运算符）调用，直到找到一个拷贝构造函数（赋值运算符）或者内置类型为止。对于内置类型，则使用从源对象到目的对象的逐位拷贝。可是。。。当你的数据成员是指针的时候呢？结果是源对象和目的对象的指针成员都指向了同一片内存空间，如果目的对象原来就有指向的内容，就产生了内存泄露，即使没有，只要有一个对象先消亡，另一个的指针就悬空了，只是多么可怕的事情啊。所以，当类中有指针成员的时候，就应该自己定义拷贝构造函数（赋值运算符），至于是简单拷贝还是引用计数，就可以根据实际情况而定了。<br>&nbsp;<br>&nbsp;&nbsp;&nbsp; 3.析构函数。缺省析构函数也不做什么特别的事情，仅仅是销毁对象。不过有点需要注意，生成的析构函数一般是非虚拟的，除非是从一个声明了虚拟析构函数的基类继承而来。析构函数，虚拟析构函数，纯虚析构函数，这三者有什么区别呢？首先，析构函数和构造函数一样，是可以沿类层次进行调用的，也就是说析构函数会从你要删除的类开始，一直向上到基类为止，以实现类层次的拆卸；但是当你希望通过基类的指针去删除派生类的对象的时候，如果没有虚拟析构函数，那么行为就是为定义的，所以当你的类中有大于等于一个虚函数的时候，你就应该立刻意识到，你需要一个虚拟析构函数，或者说，你应该保证基类拥有一个虚构造函数。那纯虚析构函数又有什么用呢？纯虚函数将产生抽象类。有些时候，你想使一个类成为抽象类，但刚好又没有任何纯虚函数。怎么办？因为抽象类是准备被用做基类的，基类必须要有一个虚析构函数，纯虚函数会产生抽象类，所以方法很简单：在想要成为抽象类的类里声明一个纯虚析构函数，是的，就是这么简单。当然啦，你还需要为这个纯虚析构函数提供一个定义，哪怕仅仅是{}，而在其派生类中，编译器会自动生成一个析构函数的定义。<br>&nbsp;<br>&nbsp;&nbsp;&nbsp; 4.取地址运算符。这个比较简单，只是单纯的返回了对象的地址，不过根据返回值是否为const还是出现了重载。<br>&nbsp;<br>&nbsp;&nbsp;&nbsp; 这里还应该考虑一种情况，有时候你可以确定不需要某些隐式生成的函数。例如在你的类中拷贝和赋值都是不允许的，这时候你就该坚决的禁止编译器生成它们。方法其实很简单，把它们声明为private，并且不要去定义。是的，就这么简单，但你试图调用的时候，编译器会哇哇大叫，你又避免了一次错误。<img src="http://www.cppblog.com/CuteSoft_Client/CuteEditor/images/emsmile.gif" align=absMiddle border=0><br><br></span>
<img src ="http://www.cppblog.com/theanswerzju/aggbug/39460.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/theanswerzju/" target="_blank">TheAnswer</a> 2007-12-24 02:51 <a href="http://www.cppblog.com/theanswerzju/archive/2007/12/24/39460.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>C++ in Details -- Item 2 : 类型转换</title><link>http://www.cppblog.com/theanswerzju/archive/2007/12/24/39459.html</link><dc:creator>TheAnswer</dc:creator><author>TheAnswer</author><pubDate>Sun, 23 Dec 2007 18:46:00 GMT</pubDate><guid>http://www.cppblog.com/theanswerzju/archive/2007/12/24/39459.html</guid><wfw:comment>http://www.cppblog.com/theanswerzju/comments/39459.html</wfw:comment><comments>http://www.cppblog.com/theanswerzju/archive/2007/12/24/39459.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/theanswerzju/comments/commentRss/39459.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/theanswerzju/services/trackbacks/39459.html</trackback:ping><description><![CDATA[<p style="FONT-SIZE: 12pt; FONT-FAMILY: courier new">&nbsp;&nbsp;&nbsp; 总是会有一些东西，我们不喜欢却躲不开，比如说考试，又比如说C++中的类型转换。作为一种强类型的编程语言，类型转换就像现实生活中变换国籍一样恐怖，不过也没办法，程序员要改变变量的属性，不是编译器能阻止的，就像张铁林徐帆之流挂着外国籍的华人演员，我们除了在心里歧视几次之外也没啥实质性的理由去谴责。C++世界和现实一样，总是有很多让我们感到无奈的地方。<br>&nbsp;<br>&nbsp;&nbsp;&nbsp; 类型转换不外乎两种：显式类型转换和隐式类型转换，下面分别说明。<br>&nbsp;<br>&nbsp;&nbsp;&nbsp; 1.显式类型转换。C++引入了四个新的类型转换符，可以替代C风格类型转换的全部功能，并提供了更强大的功能和更好的安全性，克服了C风格的类型转换目的性不明确和难以查找的缺陷。<br>&nbsp;<br>&nbsp;&nbsp;&nbsp; (1).static_cast：用于明确定义的变换，基本上和通用的C风格类型转换有着同样的能力，含义以及功能限制。包括编译器允许我们所做的不用强制转换的&#8220;安全&#8221;变换和不太安全但清楚定义的变换。<br>&nbsp;<br>&nbsp;&nbsp;&nbsp; (2).const_cast：用来改变对象的const或者volatile属性，而且由编译器强制约束语意只能做这个变换。这个转换符其实很鸡肋，因为把一个非常量指针或引用赋给一个常量指针或引用本身就是合法的；反过来的话一般来说用类型转换去掉对象的常量属性通常是不好的编程风格，因为完全有可能编译器已经把该对象存储在了只读内存中，而且就逻辑上来说很难找到去除const属性的合理解释，如果只是和类成员相关的，则应该使用mutable。<br>&nbsp;<br>&nbsp;&nbsp;&nbsp; (3).dynamic_cast：用以针对一个继承体系做向下或者横行的安全转换，并且可以知道转换是否成功，失败会通过空指针或者异常（因为没有空引用）来提醒程序员。值得注意的是这个继承体系里必须要有虚函数，因为dynamic_cast需要读取VTABLE内的信息来做判断，这里也可以引出RTTI的问题，以后再写吧。这个转换符挺实用的，也没有什么致命缺陷，就是需要一点额外的开销吧。<br>&nbsp;<br>&nbsp;&nbsp;&nbsp; (4).reinterpret_cast：重解释转换，搞不太清楚，暂时也没有发现讲的比较透彻的资料。引用Eckel的话&#8220;使用reinterpret_cast通常是一种不明智，不方便的编程方式&#8221;，不用就不会出错了。<br>&nbsp;<br>&nbsp;&nbsp;&nbsp; 2.隐式类型转换。C++的编译器和C相比，勤劳了太多太多，总是喜欢默默为我们做什么事情，太多时候这给我们带来了便利，但过犹不及，任劳任怨的编译器也会给我们带来陷进，比如说隐式类型转换。<br>&nbsp;<br>&nbsp;&nbsp;&nbsp; 在C和C++中，如果编译器看到一个表达式或者函数调用使用了一个不合适的类型，它就会尝试着执行一个自动类型转换，从现在的类型转换到所要求的类型。如果只是固有类型，一切似乎很完美，因为可以把一个int值传送给定义为double参数的函数，不过一旦涉及到了自定义类型，事情就变糟糕了。<br>&nbsp;<br>&nbsp;&nbsp;&nbsp; 有两种函数可能会导致隐式类型转换：单参数构造函数（也可以是除了第一个参数其他都有默认值的多参数构造函数）以及类型转换运算符重载函数。前者由目的类执行转换，也就是增加一个新类的时候为现有系统增加了新的转换途径，后者由源类执行转换，所以只有后者才能实现从自定义类型到内置类型的转换。<br>&nbsp;<br>&nbsp;&nbsp;&nbsp; 但是隐式转换通常都是不安全的。经常会在你根本不希望进行类型转换的时候被调用，从而可能导致错误的代码顺利通过编译或者程序的运行不符合预期，另一方面，可能会和重载解析产生冲突，这在提供了不止一种类型的自动转换而且恰好函数又被重载的时候就发生了，不过这种情况编译器会抗议，其实好于前一种情况。<br>&nbsp;<br>&nbsp;&nbsp;&nbsp; 解决方法其实并不复杂，想办法自己控制类型转换就是了，就像《C专家编程》里提到的一个重要原则：&#8220;知道自己在做什么！&#8221;所以就可以使用名字不同于语法关键字但功能相同的函数来替代转换运算符。有个经典的例子就是string类中有的是必须被显式调用的c_str()成员函数而非隐式类型转换。对于单参数构造函数，只要在函数声明前加上explicit关键字，就轻松避免了隐式类型转换，而保持了显式类型转换的合法性。<img src="http://www.cppblog.com/CuteSoft_Client/CuteEditor/images/emsmile.gif" align=absMiddle border=0><br><br></p>
<img src ="http://www.cppblog.com/theanswerzju/aggbug/39459.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/theanswerzju/" target="_blank">TheAnswer</a> 2007-12-24 02:46 <a href="http://www.cppblog.com/theanswerzju/archive/2007/12/24/39459.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>C++ in Details -- Item 1 : const的正确用法</title><link>http://www.cppblog.com/theanswerzju/archive/2007/12/24/39458.html</link><dc:creator>TheAnswer</dc:creator><author>TheAnswer</author><pubDate>Sun, 23 Dec 2007 18:42:00 GMT</pubDate><guid>http://www.cppblog.com/theanswerzju/archive/2007/12/24/39458.html</guid><wfw:comment>http://www.cppblog.com/theanswerzju/comments/39458.html</wfw:comment><comments>http://www.cppblog.com/theanswerzju/archive/2007/12/24/39458.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/theanswerzju/comments/commentRss/39458.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/theanswerzju/services/trackbacks/39458.html</trackback:ping><description><![CDATA[<p style="FONT-SIZE: 12pt; FONT-FAMILY: courier new">&nbsp;&nbsp;&nbsp; 算是前言：回头看看，这一年多来读的C++书也不算少了，但是昨天还是被Herb Sutter的《Exceptional C++》给狠狠打击了，几乎每次都踩进这位标准委员会主席的陷进之中，既有惭愧，又有弥补不足的欣喜和恍然大悟的快乐。反思一下，其实不少东西自己以前也在各种资料中看到过，但没有总结，整个C++知识体系松散而布满漏洞，所以就有了把技术上的总结记录下来的念头，一方面帮自己理清思路，另一方面也可以使我的space看起来文化层次高一些，无病呻吟少一些。似乎有个说法叫&#8220;细节决定成就&#8221;，于是我就取了个&#8220;C++ in Details&#8221;的名字，写的都是C++中的技术细节，虽然细微但肯定研究高阶技术的基石。内容和观点都是援引自读过的Bruce Eckel, Scott Meyers, Herb Sutter等大师的著作，只是做个整理，大师们应该不会和我这个小菜鸟计较版权吧。<br>&nbsp;<br>&nbsp;&nbsp;&nbsp; 言归正传，就从const的正确用法开始吧。:)</p>
<p style="FONT-SIZE: 12pt; FONT-FAMILY: courier new">&nbsp;<br>&nbsp;&nbsp;&nbsp; 总的来说，const有以下几种使用场合，值替代，指针，函数参数和返回值，类中的const，这几者概念上是一致的，但是用法却存在着细微的差别，也是本文需要说明的问题。<br>&nbsp;<br>&nbsp;&nbsp;&nbsp; 1.值替代。用以取代#define实现值替代的全部功能，作为一种更优的用法，const还可以提供类型安全以及调试时的便利，编译器也可以进行常量折叠优化。编译器通常并不为const分配存储空间，但出现取const的地址等操作时必需进行存储空间分配，这种不能完全避免导致了const的定义默认为内部连接。值得注意的是当const用于数组和结构体等集合时只意味着一块不能改变的存储区域，并不能在编译期间使用它的值。<br>&nbsp;<br>&nbsp;&nbsp;&nbsp; 2.指针。可以以&#8220;*&#8221;为分界线，const出现在左侧时修饰指针指向的对象，右侧时修饰指针本身的值，也就是指针里存储的地址。可以把一个非const对象的地址赋给一个const指针，但是不能把一个const对象的地址赋给一个非const指针，不过存在一点小小的例外，就是例如char *str = "TheAnswer"却是合法的，不过还是应该避免这种用法，用const char *str = "TheAnswer"或者char str[] = "TheAnswer"其中一个替代更好一些。<br>&nbsp;<br>&nbsp;&nbsp;&nbsp; 3.函数参数。函数参数中的const意味着变量的初值不会被函数所修改，这是一个函数作者而非函数的调用者的工具。所以当值传递的时候，参数列表中的const完全是冗余的，如果一定需要，则可以通过在函数体内定义一个指向常量参数类型的引用并初始化为参数来约束自己。当传递地址的时候，首先选择的应该是传递引用，而且是const引用。有以下几个好处，在调用者看来，效果完全等同传值传递，但是在传递大对象的时候，避免了构造函数的开销，效率上有优势；另外const引用可以接受一个临时对象作为参数，这也是指针和非const引用所无法做到的（指针需要接受一个显式的地址，而临时对象是常量，不能把一个常量赋给一个非常量引用）。<br>&nbsp;<br>&nbsp;&nbsp;&nbsp; 4.函数返回值。这里需要区分的东西更多一些。即使是按值返回，也需要区分内置类型和自定义类型，前者是否加const无所谓，因为编译器帮忙把这个返回值不让其成为右值，对于后者，const就非常重要了，因为一般来说，修改一个通过值返回的函数返回值总是一件荒谬的事情。返回地址时修改函数的返回值是具有合理性的，所以需要通过const来区分，如果返回值加上了const，那么把返回值当左值使用时就能被编译器有效的阻止。<br>&nbsp;<br>&nbsp;&nbsp;&nbsp; 5.类中的const。这里还需要两小点来看<br>&nbsp;&nbsp;&nbsp; (1).类的const数据成员。非static的const数据成员仅仅意味着在对象生命期内，这是一个常量，但是每个对象可以包含一个不同的值，所以只能在构造函数中对其进行初始化，一般放在初始化列表之中。当你想要一个编译期间的常量时，则需要使用static const，并在定义的地方便对其初始化，这可以用来取代以前的&#8220;enum hack&#8221;的小把戏。<br>&nbsp;&nbsp;&nbsp; (2).const对象和成员函数。如果声明一个成员函数为const，则该函数可以为一个const对象所调用，值得注意的是在定义函数的时候要重申const说明。由于const成员函数可以同时为const和非const对象所使用，所以应该把不修改数据成员的任何函数都声明为const。<br>&nbsp;<br>&nbsp;&nbsp;&nbsp; const可以牵扯出不少连带问题，这是下面的主要内容。<br>&nbsp;<br>&nbsp;&nbsp;&nbsp; 1.C中的const。就像马克思说的&#8220;物质决定意识，意识反作用于物质&#8221;，C++中的不少概念也渗透回了C语言，const就是其中一个。但是const在C中的含义仅仅为一个不能被改变的普通变量，甚至不能被看作编译期常量，并且在C中const被默认为是外部连接，总是会分配一块存储区域。所以C中的const用处就比C++小得多了。<br>&nbsp;<br>&nbsp;&nbsp;&nbsp; 2.临时对象。如果一个对象被创建，而又不是在堆上被创建的，而且还没有名字，那么这个对象就是临时对象。一般在为了函数调用能够成功而进行隐式类型转换和函数返回对象时的隐式类型转换时产生临时对象。前者只有在传值传递或者把对象传递给声明为const引用的参数时才会发生。<br>&nbsp;<br>&nbsp;&nbsp;&nbsp; 3.mutable和volatile。这是两个和const有密切关系的关键字。在类的声明里使用mutable，可以指定一个特定的数据成员可以在一个const对象里被改变，而且把这种常量性的缺乏公开到了类接口之中，当然这里面还涉及到按位const和按逻辑const的概念差别。volatile则告诉编译器不要擅自对该数据进行优化，因为可能存在不可知的环境改变此变量的可能。<img src="http://www.cppblog.com/CuteSoft_Client/CuteEditor/images/emsmile.gif" align=absMiddle border=0></p>
<br>
<img src ="http://www.cppblog.com/theanswerzju/aggbug/39458.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/theanswerzju/" target="_blank">TheAnswer</a> 2007-12-24 02:42 <a href="http://www.cppblog.com/theanswerzju/archive/2007/12/24/39458.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item></channel></rss>