Effective C++ Notes 5. 实现

条款26:尽可能延后变量定义式的出现时间

==> 请记住:

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

==> 解析:

定义一个变量,需要花费的成本是调用它的构造函数 + 析构函数。如果过早的定义而没有使用,则造成了不必要的浪费。


条款27:尽可能少做转型动作

==> 请记住:

(1)如果可以,尽量避免转型,特别是在注重效率的代码中避免 dynamic_cast。如果有个设计需要转型动作,试着发展无需转型的替代设计。
(2)如果转型是必需的,试着将它隐藏于某个函数背后。客户随后可以调用该函数,而不需将转型放进他们自己的代码内。
(3)宁可使用C+±style(新式)转型,不要使用旧式转型。前者很容易辨识出来,而且也比较有着分门别类的职掌。

==> 解析:

应尽量少做转型操作,因为这破坏了编译器自动保证“类型正确”的机制。

当必须执行转型时,应使用新式的转型函数。

旧式转型(C风格的)有两种写法:
① (T)expression; 如 (int*)ptr;
② T(expression); 如 int*(ptr);

这两种写法效果是等价的。

新式转型(C++风格)有四种:
① const_cast
② dynamic_cast
③ reinterpret_cast
④ static_cast

这4种显式的格式转换具有统一的格式:

cast_name<type>(expression);

① const_cast :

const_cast用于去掉某个对象的const性质,使编译器不再阻止我们对该对象进行写操作。

注意const_cast只能用于修改指针或引用类型。

使用举例:

#include 
#include 
using namespace std;

int main() {
     
	volatile const int a = 0;
	int *ptr = const_cast<int*>(&a);
	
	*ptr = 100;
	cout << a << endl;

	return 0;
}

输出结果:

100

注意一定要在 const int a前面加 volatile 修饰,否则输出结果仍是10。

关于volatile的作用:

(1)避免编译器极性默认的优化;
(2)禁止进行指令重排序。

编译器对代码做的优化种类有很多,比如 ① 去掉冗余的代码;② 对指令进行重排序;③ 当读取一个变量时,为提高存取速度,编译器有时会将变量读取到一个寄存器中,以后再读取这个变量时,就直接从寄存器中读取。

上面的示例代码中如果不加volatile修饰,则编译器会从寄存器中读取a的值,而不是内存中(编译器认为const类型的变量值是不会被修改的,所以会对其进行优化,会将其缓存在寄存器中。而实际上内存中的值已经被改写了)。
volatile则是要求编译器不要对其进行任何优化。

volatile参考内容:
http://witmax.cn/volatile.html
https://www.cnblogs.com/yuanqinnan/p/11162682.html
https://bbs.csdn.net/topics/390764610

② dynamic_cast :

用于将 基类的指针或引用 安全的转换成 派生类的指针或引用,支持运行时类型识别(RTTI = RunTime Type Identification)。

使用举例:

Base *bp;
Derived *dp = dynamic_cast<Derived*>(bp);

③ reinterpret_cast :

提供一种更低层次上的重新解释,用于不同类型的指针之间的转换。

使用举例:

int *p = &num;
char *ptr = reinterpret_cast<char*>(p);		//int*转换成char*

④ static_cast :

用于任何具有明确定义的类型转换,例如 整型、浮点型、字符型 之间的转换。

使用举例:

float num = 4.56;
int inum = static_cast<int>(num);		//float浮点型转换为int型

条款28:避免返回handles指向对象内部成分

==> 请记住:

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


条款29:为“异常安全”而努力是值得的

==> 请记住:

(1)异常安全函数(Exception-safe functions)即使发生异常也不会泄露资源或允许任何数据结构败坏。这样的函数区分为三种可能的保证:基本型、强烈型、不抛异常型。
(2)“强烈保证”往往能够以 copy-and-swap 实现出来,但“强烈保证”并非对所有函数都可实现或具备现实意义。
(3)函数提供的“异常安全保证”通常最高只等于其所调用之各个函数的“异常安全保证”中的最弱者。


条款30:透彻了解inlining的里里外外

==> 请记住:

(1)将大多数inlining限制在小型、被频繁调用的函数身上。这可使日后的调试过程和二进制升级(binary upgradability)更容易,也可使潜在的代码膨胀问题最小化,使程序的速度提升机会最大化。
(2)不要只因为function templates出现在头文件,就将它们声明为inline。

==> 解析:

由于 inline函数将“对此函数的每一个调用”都以函数本体替换之,所以这样做的后果就是增加了目标文件的大小,过度的使用inline将使可执行文件的体积变的很大。
因此 inline一般限制用在小型、被频繁调用的函数身上。


条款31:将文件间的编译依存关系降至最低

==> 请记住:
(1)支持“编译依存最小化”的一般构想是:相依于声明式,不要相依于定义式。基于此构想的两个手段是 Handle classes 和 Interface classes。
(2)程序库头文件应该以“完全且仅有声明式”(full and declaration-only forms)的形式存在。这种做法不论是否涉及templates都适用。

==> 解析:

如果想要达到真正的“接口与实现分离”,关键在于以“声明的依存性”替换“定义的依存性”,这正是编译依存性最小化的本质:
让头文件尽可能自我满足,万一做不到,则让它与其他文件内的声明式(而非定义式)相依。

① 如果使用 对象的reference 或者 pointer 可以完成任务,就不要使用 对象本身;
② 如果能够,尽量以 class 声明 替换 class 定义式;
③ 为声明式 和 定义式提供不同的头文件。

即作为接口的头文件中只存放类的public接口函数 及 一个shared_ptr 指针,在智能指针中存放类的private成员并将其放在另外的头文件中。
这样,用户代码包含的头文件中只包含了类的接口函数,只要接口函数不做修改,类的private成员的修改用户代码感知不到也无需重新编译。

int main() {
     
	Person p;		//用户代码用到了Person类,定义Person对象必须要知道Person的大小(以便分配内存)
					//而如果将类的public和private放在同一个头文件中的话,即使没有修改public接口而只是修改了private成员,也需要对用户代码重新编译
}

另一种“接口与实现分离”的方法的是需用虚基类。

你可能感兴趣的:(C/C++)