异常
我们不使用C++异常
优点:
1、 异常允许一个程序在较高层次处理深度嵌套的函数中看似不可能出现的失败,而不用模糊的失败代码。
2、 其他现代的语言基本上都使用异常:在C++中使用异常可以与Python, Java保持一致。
3、 一些第三方C++库使用异常,内在的关闭异常会使得难以与这些库兼容。
4、 异常是构造函数解决运行失败的唯一方法。我们可以用工厂方法或者Init()函数来模拟,但是需要内存分配和新的无效状态。
5、 异常在测试框架里很方便。
缺点:
1、 当你给已经存在的函数添加throw声明时,必须检查它所有的传递调用者。他们要么保证异常安全,要么接受程序的终止不捕获任何异常。
2、 通常异常使得很难通过代码来评估程序的控制流,程序可能在你不期望的地方返回。这使得很难维护和调试。你可以通过确定怎么使用和何时使用异常来减少这方面的代价,这就要求开发者需要懂得更多。
3、 异常安全需要RTII和不同的编码实践。很多的支持机制使得容易写出正确的异常安全的代码。而且,为了避免读者需要了解整个调用图,异常安全代码必需隔离逻辑写入持久状态的提交阶段。这即有好处也需要代价。使用异常我们必须为此付出代价,有时这样的代价是不值得。
4、 打开异常增加了编译时间和内存消耗。
5、 可用异常可能会鼓励开发者在不恰当和不安全的情况下使用异常。
决定:
表面上,异常的好处大于成本,尤其在新项目。然而,针对已经存在的代码,引入异常会影响所有的依赖代码。如果异常会转播出新项目,在新项目与已经存在没有异常的代码集成时也会有问题。因为大多数谷歌已存在C++代码是不打算处理异常的,它比较难以接受产生异常的代码。
假如异常存在的C++代码不容忍异常,使用异常的代价远比在新项目中使用异常大。转换过程也将是很慢的和容易出错的。我们不认为异常的可用替代方案,如错误代码和断言会引入很大的负担。
RTII运行时类型信息
定义:
RTTI 允许程序员在运行时通过typeid
或dynamic_cast
.查询C++对象。
优点:
在运行时查询对象查询对象的类型经常被认为是设计问题。需要知道对象的运行时类型,意味着你的类的顶层设计有问题。胡乱的使用RTII使代码很难维护,它导致类型决定树或者switch 声明分散在整个代码中,所有这些必须做进一步修改检查。
缺点:
RTII标准的替代方法需要修改或者重新设计类层次结构。有时这样的修改是不可行或不良的,特别是在广泛使用或者成熟的代码中。
RTII在有些单元测试中是很有用的,例如,在测试工厂类,你需要验证新产生的动态类型,它
在考虑多个虚对象时也是很有用的,
bool Base::Equal(Base* other) = 0;
bool Derived::Equal(Base* other) {
Derived* that = dynamic_cast<Derived*>(other);
if(that == NULL)
return false;
...
}
决定:
RTTI 合法但容易滥用,所以在使用时你必须小心。你可以在单元测试中自由的使用,但是在其他代码中进可能避免使用它。尤其,要在新代码中使用RTII应三思而行。当你发现需要写根据对象类型做出不同动作的代码时,考虑用以下的替代方案来查询对象类型:
1、 虚函数是一种根据不同子类允许不同代码的好方法。这使操作只存在对象内部。
2、 如果操作超出对象范围,存在其他处理代码中,考虑一种双重分发方案,例如访问者模式。
当一个程序的逻辑保证基类的一个对象实际上是子类的一个实例,那就可以自由的使用dynamic_cast
,有时也可以用static_cast
替代。
根据类型的决定树代码是不好的设计:
if (typeid(*data) == typeid(D1)) {
...
} else if (typeid(*data) == typeid(D2)) {
...
} else if (typeid(*data) == typeid(D3)) {
...
这样的代码,在添加子类的时候经常引起中断。而且,当子类的属性发生变化,这就很难找到和修改所有受影响的代码段
类型转换
用C++类型转换,如static_cast<>()。不要用其他类型转换方式如int y = (int)x;
or int y = int(x);
.
定义:
C++引入了不用于C的类型转换机制,区分类型转换操作。
优点:
C类型转换的问题是操作模糊,有时你转换 (int)3.5
),有时又(int)"hello"
)。C++避免了这种情况。此外当搜索它们时C++类型更明显。
缺点:
语法让我不爽。
决定:
不要用C方式的类型转换,而应该用C++类型转换方式替代C方式的类型转换。
1、 当你需要C风格进行值类型转换或者显示向上转换一个指针成为指向它的父类时,用static_cast
。
2、 用const_cast
取消指针的const限定。
3、 reinterpret_cast
做不安全的指针类型转换。确保你明白你在做的什么和明白混淆问题时菜用这个。
流 Streams
定义:
流是printf()
和 scanf()
.的一种替换。
优点:
有了流,你不再需要知道需要输出的对象。不会有格式字符串与参数列表不匹配的问题。流有自动的构造函数和析构函数来打开和关闭相关的文件。
缺点:
流很难做像pread()这样的功能。有些格式很难有时不可能做到除了用像printf这的函数。流不支持操作符重排序,这对国际化是有帮助的。
决定:
不要用流,除了一些需要的日志界面。应该用类似printf的函数。
用流存在各种不同的确定和优点,但是在这种情况下,就像其他大部分情况,一致性胜过雄辩。不要在你的代码中使用流。
自增和自减
对于迭代器和其他模版对象用前缀的自增和自减。
定义:
当一个变量自增或自减和表达式的值不被使用时。你必须决定是否使用前自增或后自增。
优点:
当返回值被忽略,前自增(++i
)通常比后自增(i++
)的效率好。这是因为后自增(自减)需要i的一个拷贝,也就是表达式的值。如果i是一个迭代器或者其他非标量类型,拷贝i的代价比较大。既然在返回值被忽略的情况下这两种自增方式结果是一样的,为什么不用前自增呢。
缺点:
在C才传统开发中,当表达式的值不被使用时用的是后自增。有钱是for循环中,有的人局的后自增易读,就像在英语中,i主语在++动词的前面
决定:
如果是简单的标量用哪种方式都是可以的,但是如果是迭代器或者其他模版类型,还是用前自增。