考虑使用lazy evaluation(缓式评估)

1. 引用计数

考虑下面的这段代码:

class String { ... }; 		// 一个 string 类 (the standard
							// string type may be implemented
							// as described below, but it
							// doesn't have to be)
String s1 = "Hello";
String s2 = s1; 				// 调用 string 拷贝构造函数

通常 string 拷贝构造函数让 s2 被 s1 初始化后,s1 和 s2 都有自己的”Hello”拷贝。这种拷贝构造函数会引起较大的开销,因为要制作 s1 值的拷贝,并把值赋给 s2,这通常需要用 new 操作符分配堆内存,需要调用 strcpy 函数拷贝 s1 内的数据到 s2。这是一个急式评估:只因为到 string 拷贝构造函数,就要制作 s1 值的拷贝并把它赋给 s2。然而这时的 s2 并不需要这个值的拷贝,因为 s2 没有被使用。

懒惰能就是少工作。不应该赋给 s2 一个 s1 的拷贝,而是让 s2 与 s1 共享一个值。我们只须做一些记录以便知道谁在共享什么,就能够省掉调用 new 和拷贝字符的开销。事实上s1 和 s2 共享一个数据结构,这对于 client 来说是透明的,对于下面的例子来说,这没有什么差别,因为它们只是读数据:

cout<<s1;		//读取s1的值
cout<<s1 + s2;	//读取s2的值

仅仅当这个或那个 string 的值被修改时,共享同一个值的方法才会造成差异。仅仅修改一个 string 的值,而不是两个都被修改,这一点是极为重要的。例如这条语句:

s2.convertToUpperCase();

这是至关紧要的,仅仅修改 s2 的值,而不是连 s1 的值一块修改。
为了这样执行语句,string 的 convertToUpperCase 函数应该制作 s2 值的一个拷贝,在修改前把这个私有的值赋给 s2。在 convertToUpperCase 内部,我们不能再懒惰了:必须为 s2(共享的)值制作拷贝以让 s2 自己使用。
另一方面,如果不修改 s2,我们就不用制作它自己值的拷贝。继续保持共享值直到程序退出。如果我们很幸运,s2 不会被修改,这种情况下我们永远也不会为赋给它独立的值耗费精力。

这种共享值方法的实现细节(包括所有的代码)在条款 M29 中被提供,但是其蕴含的原则就是 lazy evaluation:除非你确实需要,不去为任何东西制作拷贝。我们应该是懒惰的,只要可能就 共享使用其它值。在一些应用领域,你经常可以这么做

2. 区分读和写

继续讨论上面的 reference-counting string 对象。来看看使用 lazy evaluation 的第二种方法
考虑这样的代码:

String s = "Homer's Iliad"; // 假设是一个 reference-counted string字符串
...
cout << s[3]; 	// 调用 operator[] 读取 s[3]
s[3] = 'x';		 // 调用 operator[] 写入 s[3]

首先调用 operator[]用来读取 string 的部分值,但是第二次调用该函数是为了完成写操作。我们应能够区别对待读调用和写调用,因为读取 reference-counted string 是很容易的,而写入这个 string 则需要在写入前对该 string 值制作一个新拷贝。
我们陷入了困难之中。为了能够这样做,需要在 operator[]里采取不同的措施(根据是为了完成读取操作而调用该函数还是为了完成写入操作而调用该函数)。我们如果判断调用 operator[]的 context 是读取操作还是写入操作呢?残酷的事实是我们不可能判断出来。通过使用 lazy evaluation 和条款 M30 中讲述的 proxy class,我们可以推迟做出是读操作还是写操作的决定,直到我们能判断出正确的答案。

已标记关键词 清除标记
相关推荐
©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页