Chinaunix首页 | 论坛 | 博客
  • 博客访问: 151709
  • 博文数量: 43
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 401
  • 用 户 组: 普通用户
  • 注册时间: 2013-07-31 22:55
文章分类

全部博文(43)

文章存档

2015年(1)

2014年(25)

2013年(17)

我的朋友

分类: C/C++

2014-01-05 21:28:43

    大多数情况下,适当提出你的classes(和class templates)定义以及functions(和function templates)声明,是花费最多心力的地方。一旦正确完成它们,相应的实现大多直截了当。尽管如此,还是有些东西需要小心。太快定义变量可能造成效率上的拖延;过度使用转型(casts)可能导致代码变慢又难维护,可能招来微妙难解的错误;返回对象“内部数据之号令牌(handles)”可能会破坏封装并留给客户虚吊号码牌(dangling handles);未考虑异常带来的冲击则可能导致资源泄漏和数据败坏;过度热心地inlining可能会引起代码膨胀;过度耦合(coupling)则可能导致让人不满意的冗长建置时间(build times)
    所有这些问题都可避免,下面逐一解释各种做法。

条款26:尽可能延后变量定义式的出现时间

     只要你定义了一个变量而其类型带有一个构造函数或析构函数,那么当程序的控制流(control flow)到达这个变量定义式时,你便得承受构造成本;当这个变量离开其作用域时,你便得承受析构成本。即使这个变量最终并未被使用,仍需耗费这些成本,所以你应该尽可能避免这种情形。
     对于徃循环内的数据的字义的话,有两种方法,方法一是将对象定义在循环体外,方法二是将对象定义于循环体内部。方法一的成本是1个构造函数+1个析构函数+n个赋值操作。方法二的成本是n个构造函数+n个析构函数。你可以根据这些成本分析来决定你使用哪种方法。
    点:
  • 尽可能延后变量定义式的出现。这样做可增加程序的清晰度并改善程序效率。

条款27:尽量少做转型动作

    C++提供对旧式转型的支持,但是更推荐使用新式转型,原因是:第一,它们很容易在代码中被辨识出来。第二,各转型动作的目标越明确,编译器越可能诊断出错误的运用。
    任何一个类型转换(不论是通过转型操作而进行的显式转换,或通过编译器完成的隐式转换)往往真的令编译器编译出运行期间执行的代码。
    单一对象(假如一个类型为Derived的对象)可能拥有一个以上的地址(例如“以Base*指向它”时的地址和“以Derived*指向它”时的地址。)行为。
    dynamic_cast的许多实现版本执行速度相当慢,尝试继承或多重继承的成本更高,因此,除了对一般转型保持机敏与猜疑,更应该在注重效率的代码中对dynamic_casts保持机敏与猜疑。
    要点:
  •     如果可以,尽量避免转型,特别是在注重效率的代码中避免dynamic_casts。如果有个设计需要转型动作,试着发展无需转型的替代设计。
  •     如果转型是必要的,试着将它隐藏于某个函数背后。客户随后可以调用该函数,而不需将转型放进他们自己的代码内。
  •     宁可使用C++-styles(新式)转型,不要使用旧式转型。前者很容易辨识出来,而且也比较有着分门别类的职掌。

条款28:避免返回handles指向对象内部成分

    函数如果“返回一个handle代码对象内部成分”总是危险的,不论这所谓的handle是个指针或迭代器或reference,也不论这个handle是否为const,也不论那个返回handle的成员函数是否为const。这里唯一关键的是,有个handle被传出去了,一旦如此你就是暴露在“handle比其所指对象更长寿的”风险下。
    要点:
  •     避免返回handles(包括references、指针、迭代器)指向对象内部。遵守这个条款可增加封装性,帮助const成员函数的行为像个const,并将发生“虚吊号码牌”的可能性降至最低。


条款29:为“异常安全”而努力是值得的
    当异常被抛出时,带有异常安全性的函数应该:第一,不泄漏任何资源;第二,不允许数据败坏。他们提供以下三个保证之一:基本承诺、强烈保证、不抛出异常保证。
    有个一般化的设计策略很典型地会导致强烈保证,很值得熟悉它。这个策略被称为copy and swap。原则很简单:为你打算修改的对象(原件)做出一份副本,然后在那个副本身上做一切必要修改。若有任何修改动作招聘异常,原对象仍保持未改变状态。街所有改变都成功后,再将修改过的那个副本和原对象在一个不招聘异常的操作中转换(swap)。

    要点:
  •     异常安全函数即使发生异常也不会泄漏资源或允许任何数据结构败坏。这样的函数区分为三种可能的保证:基本型、强烈型、不抛异常型。
  •     "强烈保证"往往能够以copy-and-swap实现出来,但“强烈保证”并非对所有函数都可实现或具备现实意义。
  •     函数提供的“异常安全保证”通常最高只等于其所调用之各个函数的“异常安全保证”中的最弱者。

条款30:透彻了解inlining的里里外外

    inline只是对编译器的一个申请,不是强制命令。这项申请可以隐喻提出,也可以明确提出。隐喻方式就是将函数定义于class定义式内,明确提出是使用inline关键字。
    一个表面上看似inline的函数是否真是inline,主要取决于你的编译器。幸运的是大多数编译器提供一个诊断级别:如果它们无法将你要求的函数inline化,会给你一个警告信息。
    编译器不会对“通过函数指针而进行调用”的函数实施inline,这说明对inline函数的调用有可能被inlined,也可能不被inlined,这取决于该函数的调用方式。程序设计者必须评估“将函数声明为inline”的冲击:inline函数无法随着程序库的升级而升级。 另外,大部分调试器面对inline函数都束手无策,毕竟你没法在一个并不存在的函数内设立断点。

    要点:
  •     将大多数inlining限制在小型、被频繁调用的函数身上。这可使日后的调试过程和二进制升级更容易,也可使潜在的代码膨胀问题最小化,使程序的速度提升机会最大化。
  •     不要只因为function templates出现在头文件,就将它们声明为inline。

条款31:将文件间的编译储存关系降至最低

    “以声明依存性”替换“定义的依存性”,那正是编译依存性最小化的本质:现实中让头文件尽可能自我满足,万一做不到,则让它与其他文件内的声明式(而非定义式)相依。其他每一件事都源自于这个简单的设计策略:
    [1]如果使用object references或object pointers可以完成任务,就不要使用objects
    [2]如果能够,尽量以class声明式替换class定义式
    [3]为声明式和定义式提供不同的头文件
    在程序开发过程中使用Handle classes和Interface classes以求实现码有所变化时对其客户端带来最小冲击。而当它们导致速度和/或大小差异过于重大以至于classes之间的耦合相形之下不成为关键时,就以具象类(concrete classes)替换Handle classes和Interface classes。
    要点:
  •     支持“编译依存性最小化”的一般构想是:相依于声明式,不要相依于定义式。基于此构想的两个手段是Handle classes和Interface classes。
  •     程序库头文件应该以“完全且仅有声明式”的形式存在。这种做法不论是否涉及templates都适用。
阅读(1501) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~