从效率的观点来看,最佳的计算就是根本不计算.关键是要懒惰。lazy evaluation(懒惰计算法)。 eager evaluation(热情计算).
l 引用计数
String s1 = "Hello";
String s2 = s1; / 调用string拷贝构造函数
通常string拷贝构造函数让s2被s1初始化后,s1和s2都有自己的”Hello”拷贝。这种拷贝构造函数会引起较大的开销。然而这时的s2并不需要这个值的拷贝,因为s2没有被使用。
lazy evaluation:除非你确实需要,不去为任何东西制作拷贝。我们应该是懒惰的,只要可能就共享使用其它值。
l 区别对待读取和写入
继续讨论上面的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]
我们应能够区别对待读调用和写调用,因为读取reference-counted string是很容易的,而写入这个string则需要在写入前对该string值制作一个新拷贝。通过使用lazy evaluation和条款M30中讲述的proxy class,我们可以推迟做出是读操作还是写操作的决定,直到我们能判断出正确的答案。
l Lazy Fetching(懒惰提取)
因为LargeObject对象实例很大,为这样的对象获取所有的数据,数据库的操作的开销将非常大,特别是如果从远程数据库中获取数据和通过网络发送数据时。而在这种情况下,不需要读去所有数据。当LargeObject对象被建立时,不从磁盘上读取所有的数据,这样懒惰法解决了这个问题。不过这时建立的仅是一个对象“壳”,当需要某个数据时,这个数据才被从数据库中取回。
l Lazy Expression Evaluation(懒惰表达式计算)
Matrix<int> m1(1000, 1000); // 一个 1000 * 1000 的矩阵
Matrix<int> m2(1000, 1000); // 同上
Matrix<int> m3 = m1 + m2; // m1+m2
lazy evaluation方法说这样做工作太多,所以还是不要去做。
cout << m3[4]; // 打印m3的第四行
很明显,我们不能再懒惰了,应该计算m3的第四行值。
实际上lazy evaluation就存在于APL语言中。APL是在1960年代发展起来语言,能够进行基于矩阵的交互式的运算。那时侯运行它的计算机的运算能力还没有现在微波炉里的芯片高,APL表面上能够进行进行矩阵的加、乘,甚至能够快速地与大矩阵相除!它的技巧就是lazy evaluation。这个技巧通常是有效的,因为一般APL的用户加、乘或除以矩阵不是因为他们需要整个矩阵的值,而是仅仅需要其一小部分的值。APL使用lazy evaluation 来拖延它们的计算直到确切地知道需要矩阵哪一部分的结果,然后仅仅计算这一部分。