27.Minimize casting.(尽量少做转型动作)
C++规则的设计目标之一是,保证“类型错误”绝不可能发生。不幸的是,转型(casts)破坏了类型系统。
(1)回顾一下转型语法,通常有三种不同的形式:
1)C风格的转型动作:
(T)expression //将expression转型为T
2)函数风格的转型动作:
T(expression) //将expression转型为T
以上两种形式并无差别,纯粹只是小括号的摆放位置不同而已。我们称此两种形式为“旧式转型”。
3)C++还提供四种新式转型:
- const_cast<T>(expression)
- dynamic_cast<T>(expression)
- reinterpret_cast<T>(expression)
- static_cast<T>(expression)
其中,
a)const_cast通常被用来将对象的常量性转除(cast away the constness)。
b)dynamic_cast主要用来执行“安全向下转型”(safe downcasting),也就是用来决定某个对象是否归属继承体系中的某个类型。
c)reinterpret_cast意图执行低级转型,实际动作(及结果)可能取决于编译器,这也就表示它不可移植。
d)static_cast用来强迫隐式转换(implict conversions),例如将non-const对象转换为const对象,或将int转为double等等。
4)旧式转型仍然合法,但新式转型较受欢迎。鼓励使用新式转型,但我唯一使用旧式转型的情况是:当我要调用一个explicit构造函数将一个对象传递给一个函数时,如下
1 2 3 4 5 6 7 8 9 10 11 |
class Widget { public: explicit Widget(int size); ... }; void doSomething(const Widget& w); //以一个int加上“函数风格”的转型动作创建一个Widget doSomething(Widget(15)); //以一个int加上“C++风格”的转型动作创建一个Widget doSomething(static_cast<Widget>(15)); |
(2)优良的C++代码很少使用转型,但若说要完全摆脱它们又太过不切实际。
请记住:
(1)如果可以,尽量避免转型,特别是在注重效率的代码中避免dynamic_cast。
(2)如果转型是必要的,试着将它隐藏于某个函数背后。客户随后可以调用该函数,而不需将转型放进他们自己的代码内。
(3)宁可使用C++ style转型,不要使用旧式转型。
30.Understand the ins and outs of inlining.(透彻了解inlining的里里外外)
(1)Inline函数,看起来像函数,动作像函数,比宏好得多,可以调用它们又不需要蒙受函数调用所招致的额外开销。
(2)不仅如此,编译器最优化机制通常被设计用来浓缩那些“不含函数调用”的代码,所以当你inline某个函数,或许编译器就因此有能力对它执行语境相关最优化。
(3)inline函数背后的观念是:将“对此函数的每一个调用”都以函数本体替换之。这样做可能增加你的目标码(object code)大小。过度热衷inlining会造成程序体积太大;换个角度说,如果inline函数的本体很小,编译器针对“函数本体”所产出的码可能比针对“函数调用”所产出的码更小。
(4)记住,inline只是对编译器的一个申请,不是强制命令。这项申请可以隐喻提出也可以明确提出。
1)隐喻方式是将函数定义于class定义式内:
1 2 3 4 5 6 7 8 |
class Person { public: ... int age() const { return theAge; } //一个隐喻的inline申请 ... private: int theAge; }; |
这样的函数通常是成员函数,但条款46说friend函数也可被定义于class内,如果真的那样,它们也是被隐喻声明为inline。
2)明确声明inline函数的做法是在其定义式前加上关键字inline。
1 2 3 |
template<typename T> inline const T& std::max(const T& a, const T& b) { return a < b ? b : a; } |
inline函数通常一定被置于头文件内,因为大多数build environments在编译过程中进行inlining,而为了将一个“函数调用”替换为“被调用函数的本体”,编译器必须知道那个函数长什么样子。
3)大部分编译器拒绝将太过复杂的函数inlining,而对所有virtual函数的调用也都会使inlining落空。因为virtual直到运行期才确定调用哪个函数,而inline意味“执行前,先将调用动作替换为被调用函数的本体”。
(5)这些叙述整合起来的意思是:一个表面上看似inline的函数是否真是inline,取决于你的创建环境build environment,主要取决于编译器。辛运的是,大多数编译器提供了一个诊断级别:如果它们无法将你要求的函数inline化,会给你一个警告信息。
请记住:
(1)将大多数inlining限制在小型、被频繁调用的函数身上。这可使日后的调试过程和二进制升级(binary upgradability)更容易,也可使潜在的代码膨胀问题最小化,是程序的速度提升机会最大化。
(2)不要只因为function template出现在头文件,就将它们声明为inline。