实现

实现

尽可能延后变量定义式的出现时间

只要定义了一个变量而其类型带有一个构造函数或析构函数,那么当程序控制流到达这个变量定义式,便需要承担构造成本:当这个变量离开其作用域,便承受析构成本。即使这个变量最终并未被使用,仍然需要耗费这些成本,所以应该尽可能的避免这种情形

  • 必要时定义、延时定义

好处

  • 增加程序的清晰度并改善程序效率

尽量少做转型动作

C++ 四种转型格式

  • const_cast

    • 常量性转除
  • dynamic_cast

    • 安全向下转型(可能耗费运行时间,主要是字符串匹配)
  • reinterpret_cast

    • 低级转型,实际动作可能取决于编译器,这也就表示它不可移植,比如,void* 到 int 的转换等
  • static_cast

    • 强迫隐式转换,比如,将non-const 转换为const,int to double 等

类型转换会影响生成的代码

  • 比如:int x,y;static_cast(x)/y;//浮点运算和整型运算完全不同
  • 父类指针指向子类对象时,内部产生一个赋值操作—虚表指针
  • 转换时产生的动作可能随着编译器、平台的不同而不同,当我们使用奇淫技巧,利用编译器的这种转换时,小心

建议

  • 尽量避免转型,特别是在注重效率的代码中避免dynamic_cast,如果有个设计需要转型动作,试着发展无需转型的替代设计
  • 如果转型是必要的,试着将它藏于某个函数背后,客户随后可以调用该函数,而不需要将转型放进它们自己的代码内
  • 宁可使用c++ 风格转型,不适用旧式转型,前者容易辨别和统计

避免返回handles指向对象内部成分

核心

  • 非法引用

    • 避免,这些handle 指向了不存在的对象,容易在它们身上不产生非法访问
  • 降低封装性

    • 返回引用时,注意添加const,防止由此泄露对于私有数据、函数的访问

原则

  • 避免返回handles(包括引用、指针、迭代器)指向对象内部
  • 当此为必须的,就需要注意处理好上面的两个问题,非法引用和降低封装性

为“异常安全”而努力是值得的

异常安全的函数的特点

  • 当异常抛出的时候

    • 不泄露任何资源
    • 不允许数据败坏
  • DEMO

void C::F(std::istream& imgSrc)
{lock(&mutex);//同步
delete bgImage;//删除旧的
++imageChanges;//变更次数+1
bgImage=new Image(imgSrc);//安装新的
unlock(&mutex);//释放互斥器}
- 


	- 可能的异常点

		- new Image(imgSrc);

			- 申请新资源
			- 构造函数时,imgSrc 非法

	- 异常时的后果

		- 锁未被释放
		- bgImage 指向非法的数据
		- imageChanges 值异常
Lock ml(&mutex);
deletebgImage;
++imageChanges;
bgImage = new Image(imgSrc);
	- 解决了产生异常时,锁资源释放的问题
class C{std::tr1::shared_ptr<Image> bgImage;}
void C:F(std::istream& imgSrc)
{lock(Lock ml(&mutex);
bgImage.reset(new Image(imgSrc));//new 和初始化成功了,才调用reset 充值内部的指针
++imageChanges;}// 这里保证了imageChanges 正确性
	- 解决了bgImage 指向非法数据、数值的问题
  • 异常安全函数的保证

    • 基本承诺

      • 异常抛出的话,程序内任何事物都处于有效的状态下
    • 强烈保证

      • 如果异常抛出,程序状态不变

        • 或者完全成功,或者保持,调用函数之前的状态
    • 不抛出异常保证

      • 总是能完成它们原先承诺的功能

        • 作用域内置类型(int,指针等)身上的所有操作都提供了不抛出异常保证,这是异常安全码中一个必不可少的关键基础材料
      • 并不是说绝不会抛出异常,如果它抛出了,将是很严重的错误

  • copy and swap

    • 为打算修改的对象(原件)做出一份副本,然后在副本身上做一切必要修改。如有任何修改动作产生异常,原对象保持不改变,待所有改变都成功,再将修改过的副本和原对象在一个不抛出异常的操作中置换(swap)
    • 示例代码
struct PMImpl
{
    std::tr1::shared_ptr<Image> bgImage;
    int imageChanges;
};
class PrettyMenu {
private:
    Mutex mutex;
    std::tr1::shared_ptr<PMImpl> pImpl;
};
void PrettyMenu::changeBackground(std::istream & imgSrc)
{
    using std::swap;
    Lock ml(&mutex);
    std::tr1::shared_ptr<PMImpl> pNew(new PMImpl(*pImpl));// 生成副本
    pNew->bgImage.reset(new Image(imgSrc));// 修改副本
    ++pNew->imageChanges;
    swap(pImpl, pNew);// 置换数据,释放mutex,直到现在才访问原始数据
}

但有一个问题,当我们的函数调用了其他的实现代码, 它们的质量无法保证时,我们的代码质量就无法保证。

void SomeFunc()
{
	...
	f1();
	f2();
	...

f1、f2 可能抛出异常,如果想保证整个函数的安全性,需要想法,获得整个程序的状态(可能受到f1 和 f2 影响的状态,而它们可能影响的范围是不好确定的)。
即使它们都是“强烈异常安全”,f1、f2 可能导致程序状态改变,而SomeFunc 其他地方改变后,我们可能无法恢复到程序之前的状态。

  • 如果函数只操作局部性状态,相对容易提供强烈保证,否则,提供强烈保证就困难很多。
  • copy-and-swap 有相对严重的性能问题
  • 即使为了提供基本的保证,也需要性能、内存的代价。
  • 注意。
异常安全函数,即使发生异常也不会泄露资源或允许任何数据结构被破坏,三种等级:基本型、强烈型、不抛异常型
“强烈保证”往往能够以copy-and-swap 实现出来,但“强烈保证”并非对所有函数都可实现或具备现实意义
函数提供的“异常安全保证”通常最高只等于其所调用之各个函数的“异常安全保证”中的最弱者

透彻了解inlining

优点

  • 调用它们不需要蒙受函数调用所招致的额外开销
  • 编译器最优化机制通常被设计用来浓缩那些“不含函数调用”的代码,所以当inline 某个函数,或许编译器就因此有能力对它执行语境相关最优化

缺点

  • 代码体积

    • 潜在影响有:使代码分页,降低预取、缓存命中率等
    • 但,优化的足够好时,让函数体积比,函数调用的代码体积还要小

inline 只是对编译器的一个申请,不是强制命令,可以隐喻提出,也可以明确提出

  • 隐喻:将函数定义于class 定义式内

  • 如果friend 函数被定义于class 内,它们也是被隐喻声明为inline

  • 明确声明inline 函数,在定义式前加上inline 关键字

    • 模板也可以inline
    • 比如man 函数
  • 大部分编译器拒绝将太过复杂的函数inline,而所有对virtual 函数的调用,也会使inline 落空

    • 一个看似inline 的函数,具体,取决于编译器

      • 如果函数无法将要求的函数inline 化,将给出一个警告信息

inline 函数通常位于头文件,大多,编译过程中进行inline

  • 少量建置环境,比如.NET CLI 的托管环境,可以在运行期间完成inline

templates 通常也被置于头文件内,以为它一旦被使用,编译器为了它将具现化,需要知道它长什么样子。

有时候,虽然编译器有意愿inline 某个函数,还是可能为该函数生成一个函数本体

  • 取某个inline 函数的地址
  • 不对,通过函数指针而进行的调用,实施inlining

建议

  • 构造函数和析构函数,里面可能包含大量的代码(自己实现或编译器实现),并不是inline 的好选择

  • 评估“将函数声明为inline 的冲击

    • inline 函数无法随着程序库的升级而升级

    • 大多数调试器对inline 束手无策

      • 它们选择,调试版程序中禁止发生inlining
  • 将大多数inlining 限制在小、被频繁调用的函数身上

    • 这可使日后的调试过程和二进制升级更容易,也可使潜在的代码膨胀问题最小化,使程序的速度提升机会最大化
  • 不要因为函数模板出现在头文件,就将它们声明为inline

将文件间的编译依存关系降至最低

将接口与实现分离

  • 模块的实现修改时,不影响其他客户,它们不需要重新编译

  • 使用interface 可以完成这样的功能、同时解耦

    • 类厂
    • 多态
    • 调用成本
  • 关键,在于

    • 声明的依存性,替换,定义的依存性

建议

  • 如果使用object references 或 object pointers 可以完成任务,就不要使用objects

  • 如果能够,尽量以class 声明式,替换class 定义式

    • 声明一个函数而它用到某个类,不需要改该类的定义,纵使函数以值方式传递该类型的参数(或返回值)

      • 一旦有人调用这些函数,调用之前类定义式一定得先曝光才行

      • 并非每个人都调用这个函数

        • 将“并非真正必要之类型定义”与客户端之间的编译依存性去除掉
  • 为声明式和定义式提供不同的头文件

    • 两个头文件

      • 一个用于声明式
      • 一个用于定义式
    • 客户#include 一个声明文件而非前置声明若干函数

总结

  • 支持“编译依存性最小化”的一般构想是:相依于声明式,不相依于定义式。handle classes、interface classes
  • 程序库头文件应该以“完全且仅有声明式”的形式存在,这种做法不论是否涉及templates 都适用
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值