条款 26:尽可能延后变量定义的出现时间。
实际上就是代码规范中在使用的地方进行定义的另一种说法。目的是为了改善程序运行效率。
条款 27:尽量少做转型动作。
旧式转型:
T(expression) // 将expression转型为T
(expression)T // 将expression转型为T
两种方式无差别,C风格。
C++新式转型:
const_cast<T>(expression)
dynamic_cast<T>(expression)
reinterpret_cast<T>(expression)
static_cast<T>(expression)
各种类型转型的目的:
1. const_cast
将对象的常量属性移除,不过它不能改变对象本身的常量性,仅仅是去掉了类型的常量修饰。
const int x = 10;
int* nonConstPtr = const_cast<int*>(&x); // 去掉const属性
// *nonConstPtr = 20; // 对原本的常量对象进行修改会引发未定义行为
void print(int* num) { std::cout << *num; }
print(const_cast<int*>(&x)); // 把const int*转换为int*以便调用函数
使用提醒:
- 不要用它去修改真正的常量对象,不然会导致未定义行为。
- 它的主要用途是在函数重载时处理
const
参数。
2. dynamic_cast
安全向下转型,用来决定某对象是否归属继承体系中的某个类型。
它依赖于运行时类型信息(RTTI)。要是转换失败,对于指针会返回nullptr
,对于引用则会抛出std::bad_cast
异常。
class Base { virtual void func() {} }; // 得有虚函数才能启用RTTI
class Derived : public Base {};
Base* basePtr = new Derived;
Derived* derivedPtr = dynamic_cast<Derived*>(basePtr); // 转换成功
Base* basePtr2 = new Base;
Derived* derivedPtr2 = dynamic_cast<Derived*>(basePtr2); // 转换失败,返回nullptr
使用提醒:
- 只有类中包含虚函数时,才能使用
dynamic_cast
。 - 由于要进行运行时检查,所以会带来一定的性能开销。
3. reinterpret_cast
意图执行低级转型,实际动作及结果可能取决于编译器,这也就表示它不可移植。例如将一个point to int转型为int。
int num = 42;
int* ptr = #
long long addr = reinterpret_cast<long long>(ptr); // 把指针转换为整数
char* charPtr = reinterpret_cast<char*>(ptr); // 把int*转换为char*
使用提醒:
- 使用
reinterpret_cast
得到的结果是与具体实现相关的,很可能不具备可移植性。 - 要避免使用这种转换,除非是在进行底层编程,比如操作硬件时。
4. static_cast
用来强迫隐式转换(implicit conversions),例如将non-cast对象转换为const对象,或将int转换为double等等。它也可以用来执行上述多种转换的反向转换,例如将void *指针转换为typed指针,将pointer-to-base转为pointer-to-derived。
它主要用于进行良性转换,像基本数据类型之间的转换、子类到父类的向上转型等。不过,这种转换在运行时不会进行类型检查,所以需要你自己确保转换是安全的。
// 基本数据类型转换
double d = 3.14;
int i = static_cast<int>(d); // 把double类型转换为int类型,结果是3
// 子类到父类的向上转型(安全操作)
class Base {};
class Derived : public Base {};
Derived derived;
Base* basePtr = static_cast<Base*>(&derived); // 这种转换是安全的
使用提醒
- 不能用它来移除const属性,这得用const_cast
- 进行父类到子类的向下转型时,要保证对象实际上就是子类类型,否则会引发未定义行为。
任何一个类型转换(不论是通过转型操作而进行的显示转换,或通过编译器完成的隐式转换)往往真的灵编译器编译出运行期间执行的码。
最后建议:
建议优先使用 C++ 的新式转换,因为它们的意图更加明确,也更容易进行代码审查。
- 当需要进行常规转换时,优先考虑使用
static_cast
。 - 在继承体系中进行安全的向下转型,要使用
dynamic_cast
。 - 去掉
const
属性,就用const_cast
。 - 进行底层的强制类型转换,才使用
reinterpret_cast
,但要谨慎使用。
条款 28:避免返回 handles 指向对象内部部分。
请记住
避免返回handles(包括references、指针、迭代器)指向对象内部。
条款 29:为 “异常安全” 而努力是值得的。
请记住
-
异常安全函数(Exception-safe functions)即使发生异常也不会泄露资源或允许任何数据结构破坏。这样的函数分为三种可能得保证:基本型、强类型、不抛异常型。
-
"强烈保证"往往能够以copy-and-swap实现出来,但“强烈保证”并非对所有函数都可实现或具备现实意义。
-
函数提供的“异常安全保证”通常最高只等于其所调用之各个函数的“异常安全保证”中的最弱者。
条款 30:透彻了解 inlining 的里里外外。
请记住
-
将大多数inlining限制在小型、被频繁调用的函数身上。这可使日后的调试过程和二进制升级(binary upgradability)更容易,也可使潜在的代码膨胀问题最小化,使程序的速度提升机会最大化。
-
不要只因为function templates出现在头文件中,就将他们声明为inline。
条款 31:将文件间的编译依赖依存关系降至最低。
请记住
- 支持“编译依存性”最小化的一般构想是:相依于声明式,不要相依于定义式。基于此构想的两个手段是handle classes和interface classes.
- 程序库头文件应该以“完全且仅有声明式”(full and declaration-only forms)的形式存在。这种做法不论是否涉及templates都适用。