Effective C++ 阅读笔记·Implementations

太快定义变量可能造成效率上的拖延;过度使用转型(casts)可能导致代码变慢又难维护,又招来微妙难解的错误;返回对象“内部数据之号码牌(handles)”可能会破坏封装并留给客户虚吊号码牌(dangling handles);未考虑异常带来的冲击则可能导致资源泄露和数据败坏;过度热心地inlining可能引起代码膨胀;过度耦合(coupling)则可能导致让人不满意的冗长建置时间(build times)

26 Postpone variable definitions as long as possible

  • 尽可能延后变量定义式的出现。这样做可增加程序的清晰度并改善程序效率;

27 Minimize casting

  • const_cast 一般用于强制消除对象的常量性。它是唯一能做到这一点的 C++ 风格的强制转型;
  • dynamic_cast 主要用于执行“安全的向下转型(safe downcasting)”,也就是说,要确定一个对象是否是一个继承体系中的一个特定类型。它是唯一不能用旧风格语法执行的强制转型,也是唯一可能有重大运行时代价的强制转型;
  • reinterpret_cast 是特意用于底层的强制转型,导致实现依赖(implementation-dependent)(就是说,不可移植)的结果,例如,将一个指针转型为一个整数。这样的强制转型在底层代码以外应该极为罕见;
  • static_cast 可以被用于强制隐型转换(例如,non-const 对象转型为 const 对象,int 转型为 double,等等),它还可以用于很多这样的转换的反向转换(例如,void* 指针转型为有类型指针,基类指针转型为派生类指针),但是它不能将一个 const 对象转型为 non-const 对象(只有 const_cast 能做到),它最接近于C-style的转换;
  • const_cast,这个转换能剥离一个对象的const属性,也就是说允许你对常量进行修改;
  • static_cast,支持子类指针到父类指针的转换,并根据实际情况调整指针的值,反过来也支持,但会给出编译警告,它作用最类似C风格的“强制转换”,一般来说可认为它是安全的;
  • dynamic_cast,支持父类指针到子类指针的转换,并根据实际情况调整指针的值,和static_cast不同,反过来它就不支持了,会导致编译错误,这种转换是最安全的转换;
  • reinterpret_cast,支持任何转换,但仅仅是如它的名字所描述的那样“重解释”而已,不会对指针的值进行任何调整,用它完全可以做到“指鹿为马”,但很明显,它是最不安全的转换,使用它的时候,你得头脑清醒,知道自己在干什么;
  • 如果可以,尽量避免转型,特别是在注重效率的代码中避免使用dynamic_cast;
  • 如果必须要转型,请试着将其隐藏在某个函数背后;
  • 使用C++新型的转换,不要使用旧式转型;

28 Avoid returning “handles” to object internals

  • 避免返回handles(包括reference、指针、迭代器)指向对象内部。遵守这个条款可增加封装性,帮助const成员函数的行为像个const,并将发生”虚吊号码牌”(dangling handles)的可能性降至最低;

29 Strive for exception-safe code

  • 异常安全函数(Exception-safe functions)即使发生异常也不会泄露资源或允许任何数据结构破坏。这样的函数区分为三种可能的保证:基本型、强烈型、不抛异常型:
    基本型:如果异常被抛出,程序内的任何事物仍然保持在有效状态下。没有任何对象或数据结构会因此而败坏,所有对象都处于一种内部前后一致的状态(例如所有的class约束条件都继续获得满足);
    强烈型:如果异常被抛出, 程序状态不改变。如果函数成功,就是完全成功,否则,程序会回复到“调用函数之前”的状态;
    不抛异常型:承诺绝不抛出异常,因为它们总是能够完成它们原先承诺的功能。作用于内置类型(如ints,指针等等)上的所有操作都提供nothrow保证;
  • “强烈保证”往往能够以copy-and-swap实现出来,但“强烈保证”并非对所有函数都可实现或具备现实意义;
  • 函数提供的“异常安全保证”通常最高只等于其所调用之各个函数的“异常安全保证”中的最弱者;

30 Understand the ins and outs of inlining

  • 它们看起来像函数,动作像函数,比宏好得多,可以调用它们又不需蒙受函数调用所招致的额外开销,会增加目标码大小;
  • inline只是对编译器的申请,不是强制命令,可以隐性提出,也可以明确提出:
//隐性提出示例
class Person{
public:
    ...
    int age() const{return theAge} //隐性inline申请,定义于类内的成员函数
    ...
private:
    int theAge;
};

//显性声明inline函数的做法是在其定义式前加上关键字inline
template<typename T>
inline const T& std::max(const T& a, const T& b){
    return a < b ? b : a;
}
  • Inline函数通常一定被置于头文件内,因为大多数构建环境是在编译过程中进行inlining; inline在大多数C++程序中是编译期行为;
  • 当创建一个对象,其每一个base class及每一个成员变量都会被自动构造;当销毁一个对象,反向程序的析构行为也会自动发生;
  • 大部分调试器面对inline函数都束手无策,因为无法在一个并不存在的函数内设立断点;
  • inline:执行前,先将调用动作替换为被调用函数的本体;
  • virtual:等待,直到运行期才确定调用哪个函数;
  • 实际上构造函数和析构函数往往都是inlining的糟糕候选人;
  • 将大多数inlining限制在小型及被频繁调用的函数身上。这可使日后的调试过程和二进制升级(binary upgradability)更容易,也可使潜在的代码膨胀问题最小化,使程序的速度提升机会最大化;
  • 不要只因为function templates出现在头文件,就将它们声明为inline

31 Minimize compilation dependencies between files

  • 支持“编译依存最小化”的一般构想是:相依于声明式,不要相依于定义式。基于此构想的两个手段是Handle classesInterface classes
  • 程序库头文件应该以“完全且仅有声明式”(full and declaration-only forms)的形式存在。这种做法不论是否涉及templates都适用;
    相关博客:【1】
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值